Use PowerShell to Move Computers Based on IP Addresses: Part 2

Summary: In this blog, Eric Wright revises his script by using Active Directory cmdlets to move computers that are organized in Active Directory, based on their IP addresses.

Microsoft Scripting Guy, Ed Wilson, is here. In today’s post, guest blogger, Eric Wright, reprises yesterday’s post to use the Windows Active Directory module cmdlets. Here is a bit about Eric.

Photo of Eric Wright

I am a systems architect and blogger, and I work with Microsoft tools, Windows PowerShell, virtualization, and various web technologies. I’m a big fan of automation and scripting to simplify and enhance systems administration.
Contact information:
Website: DiscoPosse—Using the chicken to measure IT

Note: This script requires Active Directory domain controllers with Windows Server 2008 R2 or the Active Directory Management Gateway Service. For more information about installing the gateway service, see the Hey, Scripting Guy! Blog Install Active Directory Management Service for Easy PowerShell Access. With Windows Server 2008 R2, you can use the native Active Directory PowerShell module. If you are running an earlier version of Windows Server on your domain controllers, or if you do not have the Active Directory Management Gateway Service installed, you can use the process that I documented yesterday in part 1, which uses the Windows PowerShell snap-in, Quest ActiveRoles.

In my organization, I have chosen to organize my Active Directory (AD) organizational unit (OU) structure based on physical locations. A common challenge is that our technical support team does not always move computer accounts into the proper structure in Active Directory. Another issue is that computers may not be deleted from the domain when they are decommissioned. This confuses other processes that use Active Directory as their authoritative source for computer object information.

To tackle this issue, I created a Windows PowerShell script that runs as a batch process and will move the computer objects into OUs based on their IP addresses.

In my example, I am looking for only Windows 7 computers, but this can be flavored to match any selection criteria you need. The structure of the script is to do the following:

  1. Check the operating system for Windows 7 (any version).
  2. Check to see if the computer has been off the domain.
  3. If the computer has been off the network for 60 days, move it to a “Disabled” OU.
  4. If the computer has been off the network for 90 days, delete it.
  5. Check for the last DNS registration of the computer, and move it to an OU based on its IP information.

We also need to define the IP subnets and the OU structure so that we can match the computer object’s IP information and move it to its correct location in AD.

First, we load the ActiveDirectory module as follows:

Import-Module ActiveDirectory

Next, we want to define two parameters for the age of the computers. I call these $old and $veryold, and for my example, I have set them as 60 days and 90 days respectively. You can adjust these easily to suit your needs.

$old = (Get-Date).AddDays(-60) # Modify the -60 to match your threshold

$veryold = (Get-Date).AddDays(-90) # Modify the -90 to match your threshold 

Now the fun part! Because we will capture the IP information as a string and not an integer, this makes it a bit more challenging to figure out what subnet we are in. This example has three subnets, which are, and I have chosen class C subnets for this script to match my structure, but you may have to get more creative if you have a more complex network configuration.

We will define our IP range variables as Regular Expressions (or Regex as they are commonly known) so that we can match the characters appropriately. Sorry kids, but it is goodbye GUI and hello Regex for this stuff.

$Site1IPRange = “\b(?:(?:192)\.)” + “\b(?:(?:168)\.)” + “\b(?:(?:1)\.)” + “\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))” #

$Site2IPRange = “\b(?:(?:192)\.)” + “\b(?:(?:168)\.)” + “\b(?:(?:2)\.)” + “\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))” #

$Site3IPRange = “\b(?:(?:192)\.)” + “\b(?:(?:168)\.)” + “\b(?:(?:3)\.)” + “\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))” #

I know you are probably thinking it is time to just retrain the support staff to do this, right? Do not be frightened away just yet. Regex is easier than you may think once you use it more and can break it down into sensible chunks. It is as simple as reading a map (OK, that is not always simple).

Here Be Regex Dragons!

 Image of map

The key information we see is pretty readable. Because we know the first three octets are static we define them easily, as follows:

“\b(?:(?:192)\.)” + “\b(?:(?:168)\.)” + “\b(?:(?:1)\.)”

This shows us matching as 192.168.1., which takes care of the first three octets. Because it is a class C IP range, we want to capture from 0-255 in the fourth octet, which is done like this:


We read the string and look for matching of three distinct ranges, which are 0-199, 200-249, or 250-255. The OR is the important part of the phrasing, and it is represented by the | symbol, which is known as the “pipe” symbol to most.

Here is the breakdown of those three ranges:

250-255: 25[0-5]

200-249: 2[0-4][0-9]

0-199: [01]?[0-9][0-9]?

OK, we are through the tough part. Now we define the OU structure for the Disabled and the three locations:

$DisabledDN = “OU=Disabled,DC=yourdomain,DC=com”

$Site1DN = “OU=Site1,DC=yourdomain,DC=com”

$Site2DN = “OU=Site2,DC=yourdomain,DC=com”

$Site3DN = “OU=Site3,DC=yourdomain,DC=com”

This is where we begin the object query. Because we want to find out how “old” the computer account is, we will bring in the PasswordLastSet property from Active Directory, in addition to the default values. (Note that the native Microsoft parameter uses PasswordLastSet, whereas the Quest ActiveRoles parameter in yesterday’s blog uses pwdLastSet.) This will tell us the last time the hidden password that is negotiated between the computer account and Active Directory has been reset. The default maximum duration is 30 days, so as long as a computer is connecting to the domain regularly it, should always be less than 30 days old.

If you want to modify the operating systems that get captured, you simply change the selection parameters of the Get-ADComputer query as shown here:

Get-ADComputer -Filter { OperatingSystem -like “Windows 7*” } -Properties PasswordLastSet | ForEach-Object {THE REST OF OUR SCRIPT GOES IN HERE }

Our script will query AD for each Computer object, and we will run the next bunch of processes against each object in the ForEach-Object loop. All of the following content is stored inside the curly brackets.

Let’s ignore any failure messages from the IP lookup with this:

trap [System.Net.Sockets.SocketException] { continue; }

We need to use the Computer name, DN, and PasswordLastSet so let’s set those as variables from the query result. We also want to capture the current container, so we use a simple Replace command to derive the current OU location:

$ComputerName = $_.Name

$ComputerDN = $_.DN

$ComputerPasswordLastSet = $_.PasswordLastSet

$ComputerContainer = $ComputerDN.Replace( “CN=$ComputerName,” , “”)

Now we can work with the Computer account age and delete or move them as necessary:

# If the computer is more than 90 days off the network, remove the computer object

if ($ComputerPasswordLastSet -le $veryold) {

            Remove-ADObject -Identity $ComputerDN


# Check to see if it is an “old” computer account and move it to the Disabled\Computers OU

if ($ComputerPasswordLastSet -le $old) {

            $DestinationDN = $DisabledDN

            Move-ADObject -Identity $ComputerDN -TargetPath $DestinationDN

Next, we query DNS for the IP address of the computer. We will set the $IP value as $NULL first, so that if the query fails it will be dealt with correctly later in the process. If we don’t set the NULL value, it retains the IP from the last lookup, and it will move the computer incorrectly.


$IP = [System.Net.Dns]::GetHostAddresses(“$ComputerName”)

Now it is time to check for the IP range to set the destination DN accordingly. If you have a majority of systems in some network ranges, you may want to move those up to the top of the If statement so that they are processed early, which will save some time:

if ($IP -match $Site1IPRange) {

            $DestinationDN = $Site1DN


ElseIf ($IP -match $Site2IPRange) {

            $DestinationDN = $Site2DN


ElseIf ($IP -match $Site3IPRange) {

            $DestinationDN = $Site3DN


Else {

            # If the subnet does not match we should not move the computer so we do Nothing

            $DestinationDN = $ComputerContainer


And here is the last step to actually move the object to the new destination OU. This is where our NULL IP comes into play because we have assumed that if the IP is NULL, it is “off network” and the aged account process has already dealt with it:

if ($IP -ne $NULL) {

            Move-ADObject -Identity $ComputerDN -TargetPath $DestinationDN


And we made it! Another exciting tip with this script is you can run all of the ADObject cmdlets with the WhatIf parameter, which will output the result to the screen rather than perform the move or delete, so you can test drive the script before you implement it.

Here is this script in its full form from the TechNet Resources Gallery.

Thank you, Eric. This has been a great series. I appreciate you taking the time to share your knowledge with us.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (3)

  1. Curtis says:

    This doesn’t appear to work for me.. I get the following error;

    ForEach-Object : Method invocation failed because [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection] doesn’t contain a method named ‘Replace’.
    At F:PowerShell FilesMove Birmingham Computers to ComputersBirm OU.ps1:11 char:70
    + Get-ADComputer -Filter * -Properties PasswordLastSet | ForEach-Object <<<< {
    + CategoryInfo : InvalidOperation: (Replace:String) [ForEach-Object], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound,Microsoft.PowerShell.Commands.ForEachObjectCommand

  2. Ahmad Jayyusi says:

    Hi Eric,

    How can I define the sites subnets below: