Payton Flint's Tech Blog
Menu
  • Home
  • Blog
  • Categories
  • Resources
  • About
  • Contact
Menu

PowerShell – ConfigMan – Convert OUs to Collections

Posted on December 5, 2023December 5, 2023 by paytonflint

With ConfigMan application deployments, it’s good practice to, when applicable, divide large groups up and take a staged approach at deployment. I’ve found myself multiple times creating multiple collections containing the objects within an Active Directory (AD) Organizational Unit (OU) for staged deployments. I decided to build a script to ease this process.

This script allows you to provide the canonical name of an OU, recursively retrieve all of the objects within it, and checks each item for enablement in AD. Then, it checks ConfigMan for each object’s presence. Then, it divides the list into equal parts, accounting for a remainder. Finally, the script exports these smaller lists, which will be the collection members, into separate .txt files at the script root.

I didn’t go as far as to actually script the creation of the collections in ConfigMan, because the locations will likely vary significantly between uses. I also felt that removes some flexibility from the script’s use. I set the script up with the intent to copy the contents of each exported text file, to use the Add Resources option of a collection’s right-click menu, and paste the comma-separated list of names for a bulk-add.

<#
.SYNOPSIS
    Get membership of 1+ AD OUs recursively, check for AD object enablement, and ConfigMan presence.
    Divide into groups using provided collection count divisor.
    Convert lists to comma-separated names & export individual .txt files at script root.
    Lists intended for use w/ CM collection's 'Add Resources' option to copy/paste computer names for bulk add.

.NOTES
    Name: Convert-OUsToCollections
    Author: Payton Flint
    Version: 1.0
    DateCreated: 2023-Dec

.LINK
    https://github.com/p8nflnt/CM-Toolbox/blob/main/Convert-OUsToCollections.ps1
    
PowerShell – ConfigMan – Convert OUs to Collections
#> #= Prerequisites =================================================================================================================== # Clear variables for repeatability Get-Variable -Exclude PWD,*Preference | Remove-Variable -EA 0 # identify location of script $scriptPath = Split-Path ($MyInvocation.MyCommand.Path) -Parent # Install/check for ConfigurationManager module Import-Module ConfigurationManager -ErrorAction 'Stop' #= ConfigMan info ================================================================================================================== # CM Site Code $SiteCode = '<CM SITE CODE>' # CM Server Name $CMServer = '<CM SERVER>' #= OrganizationalUnit Input info =================================================================================================== # canonical name of OU(s) to search $inputOU = @('<OU CANONICAL NAME>','<OU CANONICAL NAME>') #= Collections Output info ========================================================================================================= # number of collections to create (for staged deployment) $collectionCount = '<INT>' #=================================================================================================================================== Function Convert-ADName { param ( $name, $nameType ) # check name formatting if ($name -like '*.*' -or $name -like '*/*' -or $name -like '*=*') { # Check if the input is a canonical name format if ($name -match "(^CN=.*|^OU=.*|^DC=.*)") { # replace characters for reformatting $processedCN = $name -replace ',', '' -replace 'DC=', '.' -replace 'OU=', '/' -replace 'CN=', '' # drop leading '/' if present if ($processedCN -match "^/.*") { $processedCN = ($processedCN -split '\/', 2)[1] } # split canonical name in 2 parts at first '.' $splitCN = $processedCN -split '\.', 2 # domain portion of canonical name $domain = $splitCN[1] # get remaining portion of canonical name if not empty if ($($splitCN[0]) -ne '') { # split remaining portion by '/' character $splitRemainder = $($splitCN[0]) -split '/' # invert order of the remaining items array $reversedRemainder = $splitRemainder[($splitRemainder.Length-1)..0] # reassemble in canonical name format for output $output = $domain + '/' + ($reversedRemainder -join '/') # if remainder is empty, list domain } else { $output = $domain } # if input is distinguished name format } else { # alert user to provide input type if ($nameType -ne 'container' -and $nameType -ne 'object') { Write-Host -ForegroundColor Red "Distinguished name format detected.`r`n-inputType must be set to 'Container' or 'Object'." } # glitch in replace, had to invoke method this way # replace characters for reformatting $processedDN = $($name -replace '/', ',OU=').Replace('.', ',DC=') # if 'OU=' is present if ($processedDN -match ".*OU=.*") { # split in 2 parts at first ',OU=' $splitDN = $processedDN -split '\,OU=', 2 # domain portion of distinguished name $domain = $splitDN[0] # split remaining portion by ',OU=' $splitRemainder = $($splitDN[1]) -split ',OU=' # invert order of the remaining items array $reversedRemainder = $splitRemainder[($splitRemainder.Length-1)..0] # reassemble in distinguished name format for output $reassembledDN = ($reversedRemainder -join ',OU=') + ',DC=' + $domain # add appropriate prefix to distinguished name and output if ($nameType -eq 'object') { $output = 'CN=' + $reassembledDN } elseif ($nameType -eq 'container' ) { $output = 'OU=' + $reassembledDN } # if remainder is empty, list domain } else { $output = 'DC=' + $processedDN } } return $output # warn on invalid name format } else { Write-Host -ForegroundColor Red "Invalid name format." } } # end Convert-ADName function Function Split-List { param ( $inputList, [int]$divisor ) # initialization $lists = @() $startIndex = 0 $listArray = @() # Calculate the count of each list & remainder $listSize = [math]::floor($inputList.Count / $divisor) $remainder = $inputList.Count % $divisor # Iterate through each list for ($i = 0; $i -lt $divisor; $i++) { # Calculate the size of the current list $size = $listSize + [math]::min(1, $remainder) # Decrease the remainder by 1 if remainder $remainder = [math]::max(0, $remainder - 1) # Clear the current list array $listArray = @() # Add items to the current list array $listArray += $cmPresent[$startIndex..($startIndex + $size - 1)] # Add the current list array to lists $lists += ,$listArray # Update the start index for the next list $startIndex += $size } return $lists } # end Split-List function # initialize arrays $inputDN = @() $objDNs = @() $computerNames = @() $enabledNames = @() $cmPresent = @() # convert canonical name to distinguished name & add to array $inputOU | ForEach-Object { $inputDN += Convert-ADName -name $_ -nameType $nameType } # get all object distinguished names recursively within OU & filter containers/duplicates $inputDN | ForEach-Object { $objDNs += (Get-ADObject -Filter * -SearchBase $_).DistinguishedName | Where-Object {$_ -match "^CN=.*" -and $_ -notlike "*{*}*"} } # get results in canonical name format $objCNs = $objDNs | ForEach-Object { Convert-ADName -name $_ } # separate computer names & add to array $objCNs | ForEach-Object { if ($_ -match '\/([^/]+)$') { $computerNames += $matches[1] } } # add enabled computerNames to array $computerNames | ForEach-Object { $enabledStatus = $null $enabledStatus = $(Get-ADComputer -Filter { Name -eq $_ }).Enabled if ($enabledStatus) { $enabledNames += $_ } } # precautionary deduplication and sort $enabledNames = $enabledNames | Select-Object -Unique | Sort-Object # ConfigMan presence check # append ':' to site code if ($SiteCode -notlike "*:") { $SiteCode = "$SiteCode" + ':' } # connect to CM Site Set-Location $SiteCode # check for presence in CM $enabledNames | ForEach-Object { # initialize variable for loop $cmCheck = $null # check for device name in CM $cmCheck = Get-CMDevice -Fast -Name $_ # if present, add to array if ($cmCheck) { $cmPresent += $_ } } # return to system drive Set-Location $env:SystemDrive # separate list $collections = Split-List -inputList $cmPresent -divisor $collectionCount # Export each collection to a separate text file for ($i = 0; $i -lt $collectionCount; $i++) { $collection = $collections[$i] $filePath = $scriptPath + "\Collection_$($i + 1).txt" # Join the computers in the collection with a comma and export to a text file $collection -join ',' | Out-File -FilePath $filePath -Encoding UTF8 Write-Host -ForegroundColor Green "Exported $filePath" }

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

About The Author

Author's Portrait

In my journey as a technologist and 11 years of experience as an IT professional, I have found my niche as Director of Infrastructure Services; developing my skillsets in management, scripting, cloud infrastructure, identity management, and networking.

I have experience as a Systems Administrator and Engineer for large enterprises including the DoD, government agencies, and a nuclear-generation site.

I've been blessed to collaborate with engineers at esteemed Fortune 50 corporations, and one of Africa's largest, to ensure successful implementation of my work.

GitHub Button

Credentials

M365 Endpoint Administrator Associate
M365 Fundamentals
Microsoft AZ-900
CompTIA CSIS
CompTIA CIOS
CompTIA Security+
CompTIA Network+
CompTIA A+
  • April 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
© 2022 Payton Flint | The views and opinions expressed on this website belong solely to the author/owner and do not represent the perspectives of any individuals, institutions, or organizations, whether affiliated personally or professionally, unless explicitly stated otherwise. The content and products on this website are provided as-is with no warranties or guaranties, are for informational/demonstrative purposes only, do not constitute professional advice, and are not to be used maliciously. The author/owner is not responsible for any consequences arising from actions taken based on information provided on this website, nor from the use/misuse of products from this site. All trademarks are the property of their respective owners.