Remove SCCM Old Computer Objects Based on SIDs

There are dozens of blog posts and instructions on how to remove computer objects from Configuration if the corresponding AD object no longer exists. I thought I would put my own spin on the idea since I haven’t seen it before. Many of the guides rely on matching the object’s names to each other. The problem you can run into with this method is reusing computer names. You can delete a computer in AD and add a new one with the same computer name. When Configuration Manager runs its next AD sync it will find the new AD computer object and add it to the inventory. Now Configuration Manager has two computer objects with the same name. As an administrator this can get confusing and if you try to clean up Configuration Manager based on name matching the old object won’t get removed.

Rather than match the objects on name you can use something more unique, like the SID. Configuration Manager capture the AD objects SID during the sync so you can use that to match objects and delete those that no longer exist. You can look at the code below or in github.

Find the PowerShell script at https://github.com/agizmo/SCCM_Computer_Removal_SID

$SiteCode = "<YOUR SITE CODE>" # Site code
$ProviderMachineName = "<YOUR CM SERVER>" # SMS Provider machine name

#Customizations
$initParams = @{}

#Import the ConfigurationManager.psd1 module
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)..\ConfigurationManager.psd1" @initParams
}

#Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}

#Set the current location to be the site code.
Set-Location "$($SiteCode):\" @initParams

#As far as I can tell, this is the only way to get the SID for a computer object in SCCM. The prebuilt cmdlets will not return SID
$devices = Get-WmiObject -ComputerName $ProviderMachineName -Namespace "ROOT\SMS\Site_$SiteCode" -Class SMS_R_System
foreach ($device in $devices) {
try {
$sid = new-object System.Security.Principal.SecurityIdentifier($device.SID)
} catch {}
$ADcomputer = Get-ADComputer -Filter {SID -eq $sid} if ($ADcomputer) { #nothing } else { Remove-CMResource -ResourceId $device.ResourceId -Force } Remove-Variable sid Remove-Variable ADcomputer
}

Simple as that. Hope you found this article helpful. And play around with PowerShell. There is near infinite capabilities of the language.

Have fun.
-Tony

Updating Ruckus AP DNS Settings

I ran into an issue recently while trying to do some cleanup on a Ruckus SmartZone wireless controller. We migrated to new AD servers, which also doubled as DNS servers and included assigning new IP addresses to our controllers. All devices on our network with static IP settings needed to be updated. Servers and switching hardware weren’t difficult because we could script the change. Our Ruckus APs were more challenging. With 400+ APs to touch, making the change by hand wasn’t practical. There had to be a way to script the change. No problem. The Ruckus SmartZone controller had an SSH interface. It was pretty easy to get into settings of an AP config, so I could just change the DNS settings, right?

Or Not. It turns out you can’t just change the IP settings of an AP on the SmartZone controller. You can swap from static to DHCP and back, but that results in two reboots of the AP as it reads the config changes. There had to be another way.

I opened a ticket with Ruckus support to see if they had any suggestions. One option proposed was to use the “remote ap-cli” command to set the DNS settings directly on the APs themselves rather than in the config on the controller. Was that a solution? Yes, but if that APs were ever to reset, they would get the old settings from the controller. There had to be another way.

I then talked to our Ruckus Systems Engineer about the problem. He suggested I look at the SmartZone API. The API did have a command to change the IP settings, so I set out figuring out how to use it.

NOTE: Today, you can read through the SmartZone API documentation without a Ruckus support account, but I’ve been told that may change in the future. http://docs.ruckuswireless.com/smartzone/5.1.1/vszh-public-api-reference-guide-511.html

Now, I am not a developer or programmer. My official code learning stopped at VB.Net in 2005, but I’ve been using PowerShell for years to perform Windows and Active Directory management. From a talk I watched by Jeffery Snover (father of PowerShell), I know there are two useful functions I can use to talk with web-based APIs: Invoke-WebRequest & Invoke-RestMethod. Invoke-WebRequest can be used to get a session cookie needed to execute other API commands. Invoke-RestMethod is very similar to Invoke-WebRequest but can automatically parse JSON or XML data and turn them into PSObjects. With that knowledge in hand, I got started.

First, I had to get a web session.

$uri = "https://:8443/wsg/api/public"
$logonuri = $uri+"/v8_1/session"

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json;charset=UTF-8")

#logon
$body = @{"username"="admin";
    "password"="";
    "timeZoneUtcOffset"="-05:00"
    }

$json = $body | ConvertTo-Json


###Requesting a web sessions and adding the cookie to the header file doesn't work in Windows PowerShell 5.1
###Instead HTTPWebRequest from .NET has to be used and pass that session to all the subsequent PowerShell commands.
$webrequest = Invoke-WebRequest -Uri $logonuri -Headers $headers -Body $json -Method Post -SessionVariable websession

Next, I retrieved a list of all the APs managed by the controller. By default, the command paged the result to 100 items at a time, but I could easily loop to capture all the APs.

###Get all the APs on the controller###
$index = 0
$aps = @()
$apsuri = $uri+"/v8_1/aps?index=$index"

do {
    $response = Invoke-RestMethod -Method Get -Uri $apsuri -WebSession
    $websession -Headers $headers
    $aps += $response.list
    $index += 100
    $apsuri = $uri+"/v8_1/aps?index=$index"
} while ($response.hasMore)

Now all I had to do was retrieve the IP settings of each AP, create a JSON object with the new IP settings, and PATCH is back to the controller

#Example DNS addresses
$DNS1 = "1.1.1.1"
$DNS2 = "1.0.0.1"

foreach ($ap in $aps){
    $apuri = $uri+"/v8_1/aps/"+$ap.mac
    $ap = Invoke-RestMethod -Method Get -Uri $apuri -WebSession $websession -Headers $headers
    $oldnetwork = $ap.network

    if ($oldnetwork.ipType -eq "Static") {
        $newnetwork = @{
            "ipType"="Static"; "ip"=$oldnetwork.ip;
            "netmask"=$oldnetwork.netmask;
            "gateway"=$oldnetwork.gateway;
            "primaryDns"=$DNS1;
            "secondaryDns"=$DNS2;
        }
        $networkjson = $newnetwork | ConvertTo-Json

        $networkuri = $apuri+"/network"
        Write-Host "Updating $($ap.name)"
        Invoke-RestMethod -Method Patch -Uri $networkuri -WebSession $websession -Headers $headers -Body $networkjson 

        Remove-Variable networkjson
    } else {
        Write-Host "$($ap.name) is not using static IP settings"
        $notstatic += $ap
    }
}

That’s it. In a matter of fewer than 60 seconds, I was able to update the DNS settings on all the APs managed by our Ruckus Smartzone controller. This was just the start of what is possible through the Smartzone API. If I only wanted to modify APs in a specific zone, I could narrow the AP retrieval down by performing something like this.

###Get all the APs of a zone###
...
$apsuri = $uri+"/v8_1/aps?index=$index&zoneId=<INSERT ZONE ID HERE>"
...

I hope you found this interesting. If you want a complete PowerShell file to play around with, check out my GitHub repository found at https://github.com/agizmo/SmartZone-AP-DNS-Update

Have Fun.
-Tony