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

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

  • Comments 6
  • Likes

Summary: In this guest blog post written by Eric Wright, you will learn how to use the Windows PowerShell snap-in, Quest ActiveRoles, to move computers that are organized in Active Directory, based on their IP addresses.

Microsoft Scripting Guy, Ed Wilson, is here. Guest Blogger Weekend continues. Today we have a guest blog written by Eric Wright. Here is what Eric has to say about himself.

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
Twitter: http://www.twitter.com/discoposse
LinkedIn: http://ca.linkedin.com/pub/eric-wright/3/7b4/bb6

Note: This script uses the Windows PowerShell snap-in, Quest ActiveRoles. This process will work with any version of Windows Server, but you must use Quest ActiveRoles if you are running a version earlier than Windows Server 2008 R2 on your domain controllers. Tomorrow in Part 2 of this blog, I document the exact process, but we use Active Directory domain controllers with Windows Server 2008 R2 or the Active Directory Management Gateway Service.

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.

For our script to work, we need to have the Windows PowerShell snap-in, Quest ActiveRoles, installed on the computer that will be running the script for us. 

If you are running Active Directory Domain Controllers with Windows Server 2008 R2, you can use the native ActiveDirectory Windows PowerShell module. I will post the script for using the Windows Server 2008 R2 module tomorrow in part 2 of this series.

We will 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 Quest ActiveRoles snap-in as shown here:

Add-PsSnapIn Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue

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 will capture the IP information as a string and not as an integer, this makes it a bit more challenging to figure out what subnet we are in. This example has three subnets: 192.168.1.0/24, 192.168.2.0/24, and 192.168.3.0/24. 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]?))" # 192.168.1.0/24

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

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

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 here 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:

“\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))”

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 pwdLastSet property from Active Directory, in addition to the default values. (Note that the Quest ActiveRoles parameter uses pwdLastSet, whereas the native Microsoft parameter in tomorrow’s blog uses PasswordLastSet.) 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-QADComputer query as shown here:

Get-QADComputer -ComputerRole member -IncludedProperties pwdLastSet -SizeLimit 0 -OSName 'Windows 7*' | 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 pwdLastSet, 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 = $_.pwdLastSet

$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-QADObject -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-QADObject -Identity $ComputerDN -NewParentContainer $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 = $NULL

$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

}

Let’s do a health check on our IP selection:

 Image of command output

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-QADObject -Identity $ComputerDN -NewParentContainer $DestinationDN

}

And we made it! Another exciting tip with this script is that you can run all of the QADObject 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.

You can download the script in its full form from the TechNet Resources Gallery.

~Eric

Thank you, Eric. This is a really cool solution to a rather common problem. I am looking forward to part 2 tomorrow from Eric.

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

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Eric,

    thank you for publishing this solution (in two different "colors") here!

    It's a great idea to sort out the old computer AD accounts and rearrange computer accounts that are accidentally assigned to wrong containers, If you can identify wrong assignments by IP you can move them out of their current container and into another "the right" container easily!

    If you allow to add some thoughts here ... I'd like to comment some issues!

    Your script depends on IP V4 numbers which maybe a problem today ... that's something to remember.

    Another problem is the way the IP addresses are identified

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

    [DBG]: PS C:\Users\Schulte>>> $ip

    Address           :

    AddressFamily     : InterNetworkV6

    ScopeId           : 11

    IsIPv6Multicast   : False

    IsIPv6LinkLocal   : True

    IsIPv6SiteLocal   : False

    IPAddressToString : <IMy P V6 Address>%11

    Address           : 654973612

    AddressFamily     : InterNetwork

    ScopeId           :

    IsIPv6Multicast   : False

    IsIPv6LinkLocal   : False

    IsIPv6SiteLocal   : False

    IPAddressToString : <My IP V4 Address>

    $IP returns an array which is something to consider!

    The first array element $IP[0] does show up an IPV6 address on my workstation.

    $IP.Count is 2 on my workstation

    $ip -match $Site-x-IPRange doesn't return anything in this case!

    In general $IP returns an array of type IPAddress and you are interested in the member "IpAddressToString" so it might be a very good idea to match an array element against this value like

    $ip[1].IpAddressToString -match $Site-x-IPRange

    So we might have to filter the $IP array to retrieve the IPV4 addresses before we try to match them against the regexp

    $ipV4 = $ip | ?{ $_.AddressFamily -eq 'InterNetwork' }

    A last thing about using regexps here:

    I'm really a prooven regexp-fan ( can anybody here confirm that !? )

    BUT: I really wouldn't use them here! They are oversized if you ask me!

    Doing a plain and easy compare like that

    switch -wildcard ($ipv4.IpAddressToString) {

     '192.168.1.*' { $DestinationDN = $Site1DN }

     '192.168.2.*' { $DestinationDN = $Site2DN }

     '192.168.3.*' { $DestinationDN = $Site3DN }

     default           { $DestinationDN = $ComputerContainer }

    }

    will definitely be easier to read ... understand and maintain!

    Of course you can use variables instead of the constants here

    and if things are getting more complicated we can excahnge the -wildcard switch

    with the -regexp switch to handle these situations.

    Klaus.

  • Thank you for this great script, i've customised my sites and created the relevant RegEx entries but I notice that occasionally the script will error out. It appears to try to move the computer twice and gets mixed up?

    COMPUTER1                computer        CN=COMPUTER1,OU=OriginalOU,DC=domain,DC=com                                                                                                              

    Cannot resolve directory object for the given identity: 'CN=COMPUTER1,OU=Workstations,OU=Head Office,DC=domain,DC=com'.

    At C:\Script\PowerShell-ActiveDirectoryOrganizeWin7ByIP-QuestActiveRoles.ps1:163 char:23

    +         Move-QADObject <<<<  -Identity $ComputerDN -NewParentContainer $DestinationDN

       + CategoryInfo          : NotSpecified: (:) [Move-QADObject], ObjectNotFoundException

       + FullyQualifiedErrorId : Quest.ActiveRoles.ArsPowerShellSnapIn.DirectoryAccess.ObjectNotFoundException,Quest.ActiveRoles.ArsPowerShellSnapIn.Cmdlets.MoveObjectCmdlet

  • Hi Peter,

    I had the same issue.

    The error occurs when you have a computer account that is "old" but actually still has a record in DNS.

    As a result, the computer is moved into the "OLD" OU (as designed) but then also tries to move it into the according site based on IP (cause the IP is not NULL). By the time it attempts this task, the computer account has already been moved (resulting in the error)

    The fix is simple.

    Change the following line:

    if ($IP -ne $NULL) {

    Move-QADObject -Identity $ComputerDN -NewParentContainer $DestinationDN

    }

    to this:

    if (($IP -ne $NULL) -and ($ComputerPasswordLastSet -ge $old)) {

    Move-QADObject -Identity $ComputerDN -NewParentContainer $DestinationDN

    }

    This will only run the move process if both IF criterias are met.

    Basically...

    Only move computer accounts with IP address that are NOT classified as an "old" computer.

    Scenarios:

    -If the IP is NULL - do nothing (as we assume the "old" computer task has moved it into the disabled OU if it is infact inactive)"

    -If the IP is present, but the computer is "old" - do nothing (as the "old" computer task has moved it into the disabled OU already)

    If IP is present, and the computer is current - move to specific Site OU.

    Thanks

  • Wouldn't it be easier to use the SUBNET to classify what OU to direct accounts into? That way you wouldn't have to mess with much regex.

  • Hi
    thanks for sharing this article
    do you know any powershell command to find live IP address from 192.168.1.1 to 192.168.255.255 on network ?
    right now I do it with some tools like angry IP scanner or radmin advance port scanner
    but I'm look fo script to do it ?
    help me if you know any script to range all live ip on the internal or external (interanet or internet ) network ?
    Again thanks
    Regards :
    Raha

  • thank you