Recently, I wanted to clean up SCCM of a bunch of systems that still reside in active directory, but are also disabled. The first thing I did was to try and query SCCM for a list of systems that were populated via AD, but have a userAccountControl attribute of 4098. The attribute is normally 4096, but it changes to 4098 when the account is disabled. I learned the userAccountControl is populated in SCCM via AD, but there is a catch. It is only populated while the account is active. If the account is deactivated, SCCM cannot read the 4098 value, therefore it will still read inside SCCM as 4096.
The next thing was to use PowerShell as the connector between AD and SCCM to clean these items out of SCCM. The script below queries the designated $SCCMCollection, preferably 'All Systems', for a list of all systems in the All Systems collection. That collection is populated by Active Directory. Once it gets a list of all systems within that collection, it will then check if the -ReportOnly parameter is selected and will only display a report of the systems with the system name and if it is enabled or not.
If -ReportOnly is not defined, then the script will go through the list and remove machines from SCCM that are disabled in AD.
After I ran this script, I ran the Active Directory System Discovery against AD. The deleted systems did not return in SCCM.
To run this script, you will need RSAT installed. I have included the SCCM module locator in the script that will locate the .psd1 module to use with the script. All you need to do is to define the $SCCMServer with the SCCM server name, $SCCMDrive with the SCCM drive name, and the name of the desired collection in $SCCMCollection; I used All Systems. I ended up using Orchestrator to execute the script once a week. You could also use a scheduled task. Also, you will need RSAT installed on the system this script is executed on.
You can download this script from my GitHub site located here.
The next thing was to use PowerShell as the connector between AD and SCCM to clean these items out of SCCM. The script below queries the designated $SCCMCollection, preferably 'All Systems', for a list of all systems in the All Systems collection. That collection is populated by Active Directory. Once it gets a list of all systems within that collection, it will then check if the -ReportOnly parameter is selected and will only display a report of the systems with the system name and if it is enabled or not.
If -ReportOnly is not defined, then the script will go through the list and remove machines from SCCM that are disabled in AD.
After I ran this script, I ran the Active Directory System Discovery against AD. The deleted systems did not return in SCCM.
To run this script, you will need RSAT installed. I have included the SCCM module locator in the script that will locate the .psd1 module to use with the script. All you need to do is to define the $SCCMServer with the SCCM server name, $SCCMDrive with the SCCM drive name, and the name of the desired collection in $SCCMCollection; I used All Systems. I ended up using Orchestrator to execute the script once a week. You could also use a scheduled task. Also, you will need RSAT installed on the system this script is executed on.
You can download this script from my GitHub site located here.
<#
.SYNOPSIS
SCCM AD Cleanup
.DESCRIPTION
This script removes systems from SCCM that are populated via active directory, but have been disabled in AD.
.PARAMETER SCCMServer
Name of the SCCM server
.PARAMETER SCCMDrive
SCCM Drive
.PARAMETER SCCMCollection
SCCM collection to query for cleanup
.PARAMETER ReportOnly
Produce a report only
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.143
Created on: 8/3/2017 9:07 AM
Created by: Mick Pletcher
Filename: SCCMADCleanup.ps1
.EXAMPLE
powershell.exe -file SCCMADCleanup.ps1 -SCCMServer AtlantaSCCM -SCCMDrive ATL
===========================================================================
function Get-RelativePath {
<#
.SYNOPSIS
Get the relative path
.DESCRIPTION
Returns the location of the currently running PowerShell script
.NOTES
Additional information about the function.
#>
[CmdletBinding()][OutputType([string])]
param ()
$Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"
Return $Path
}
function Import-SCCMModule {
<#
.SYNOPSIS
Import SCCM Module
.DESCRIPTION
Locate the ConfigurationManager.psd1 file and import it.
.PARAMETER SCCMServer
Name of the SCCM server to connect to.
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()][string]$SCCMServer
)
#Get the architecture of the specified SCCM server
$Architecture = (get-wmiobject win32_operatingsystem -computername $SCCMServer).OSArchitecture
#Get list of installed applications
$Uninstall = Invoke-Command -ComputerName $SCCMServer -ScriptBlock { Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Force -ErrorAction SilentlyContinue }
If ($Architecture -eq "64-bit") {
$Uninstall += Invoke-Command -ComputerName $SCCMServer -ScriptBlock { Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" -Force -ErrorAction SilentlyContinue }
}
#Get the registry key that specifies the location of the SCCM installation drive and directory
$RegKey = ($Uninstall | Where-Object { $_ -like "*SMS Primary Site*" }) -replace 'HKEY_LOCAL_MACHINE', 'HKLM:'
$Reg = Invoke-Command -ComputerName $SCCMServer -ScriptBlock { Get-ItemProperty -Path $args[0] } -ArgumentList $RegKey
#Parse the directory listing
$Directory = (($Reg.UninstallString).Split("\", 4) | Select-Object -Index 0, 1, 2) -join "\"
#Locate the location of the SCCM module
$Module = Invoke-Command -ComputerName $SCCMServer -ScriptBlock { Get-ChildItem -Path $args[0] -Filter "ConfigurationManager.psd1" -Recurse } -ArgumentList $Directory
#If more than one module is present, use the latest one
If ($Module.Length -gt 1) {
foreach ($Item in $Module) {
If (($NewModule -eq $null) -or ($Item.CreationTime -gt $NewModule.CreationTime)) {
$NewModule = $Item
}
}
$Module = $NewModule
}
#format the $Module unc path
[string]$Module = "\\" + $SCCMServer + "\" + ($Module.Fullname -replace ":", "$")
#Import the SCCM module
Import-Module -Name $Module
}
function Get-SCCMCollectionList {
<#
.SYNOPSIS
Retrieve Collection List
.DESCRIPTION
Query the specifies collection in SCCM for a list of all systems residing in that collection.
.PARAMETER CollectionName
Name of SCCM collection to query
.EXAMPLE
PS C:\> Get-SCCMCollectionList
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()][string]$CollectionName
)
#FQDN of SCCM Server
$FQDN = ([System.Net.Dns]::GetHostByName($SCCMServer)).HostName
#Create new SCCM drive
New-PSDrive -Name $SCCMDrive -PSProvider "AdminUI.PS.Provider\CMSite" -Root $FQDN -Description $SCCMDrive"Primary Site" | Out-Null
#Add colon at end of SCCMDrive if it does not exist
If ($SCCMDrive[$SCCMDrive.Length - 1] -ne ":") {
$SCCMDrive = $SCCMDrive + ":"
}
#Change to SCCM drive
Set-Location $SCCMDrive
#Get the collection ID for retrieving the list of systems
$CollectionID = (Get-CMDeviceCollection | Where-Object { $_.Name -eq $SCCMCollection }).CollectionID
#Get list of systems from the specified collection
$CollectionSystems = (Get-CMDevice -CollectionId $CollectionID).Name | Where-Object { $_ -notlike "*Unknown Computer*" } | Sort-Object
#Change location to the local drive
Set-Location $env:HOMEDRIVE
#Create Collection array
$Collection = @()
foreach ($System in $CollectionSystems) {
try {
$ADSystem = (Get-ADComputer $System).Enabled
} catch {
$ADSystem = $false
}
$objSystem = New-Object System.Object
$objSystem | Add-Member -MemberType NoteProperty -Name Name -Value $System
$objSystem | Add-Member -MemberType NoteProperty -Name Enabled -Value $ADSystem
$Collection += $objSystem
}
Return $Collection
}
function Remove-Systems {
<#
.SYNOPSIS
Remove Disabled Systems
.DESCRIPTION
Remove disabled active directory systems from SCCM
.PARAMETER Collection
List of all machines in the $SCCMCollection
.EXAMPLE
PS C:\> Remove-Systems
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]$Collection
)
#Add colon at end of SCCMDrive if it does not exist
If ($SCCMDrive[$SCCMDrive.Length - 1] -ne ":") {
$SCCMDrive = $SCCMDrive + ":"
}
#Change to SCCM drive
Set-Location $SCCMDrive
#Parse through list and delete systems from SCCM
foreach ($System in $Collection) {
If ($System.Enabled -eq $False) {
Remove-CMDevice -Name $System.Name -Force
}
}
#Change location to the local drive
Set-Location $env:HOMEDRIVE
}
Clear-Host
Import-Module ActiveDirectory
Import-SCCMModule -SCCMServer $SCCMServer
$Collection = Get-SCCMCollectionList -CollectionName "All Systems"
If (!($ReportOnly.IsPresent)) {
Remove-Systems -Collection $Collection
$Collection
$Collection | Out-File -FilePath $File -Encoding UTF8 -NoClobber -force
} else {
#Get execution path of this script
$RelativePath = Get-RelativePath
#Location and name of .CSV to write the output to
$File = $RelativePath + "DisabledSystems.csv"
#Delete file if it exists
If ((Test-Path $File) -eq $true) {
Remove-Item -Path $File -Force
}
$Collection
$Collection | Out-File -FilePath $File -Encoding UTF8 -NoClobber -force
}
0 comments:
Post a Comment