DSCResources/cCMCollection/cCMCollection.psm1
###################################################################################### # The Get-TargetResource cmdlet. # This function will get the collection if it exists and return all information ###################################################################################### function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String] $CollectionName, [parameter(Mandatory = $true)] [PSCredential] $SCCMAdministratorCredential, [ValidateSet("1","2")] [System.String] $CollectionType = "2" ) #Login ($oldToken, $context, $newToken) = ImpersonateAs -cred $SCCMAdministratorCredential #Load Module if missing then set the location for execution if(!(Get-Module ConfigurationManager)) { Try { Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1') } Catch { Throw "Cannot load the SCCM Module, please ensure the SCCM Admin tools are installed and try again" } } $ComputerInfo = Get-WmiObject Win32_ComputerSystem $ComputerFQDN = $ComputerInfo.Name + '.' + $ComputerInfo.Domain $CM12ProviderLocation = Get-WmiObject -Query "Select * From SMS_ProviderLocation where ProviderForLocalSite = True" -Namespace "root\sms" -computername $ComputerFQDN $Site = $CM12ProviderLocation.SiteCode if(!((Get-PSDrive) -like $Site)) { throw "Problems discovering a valid Site. Please Investigate." } $OriginalLocation = Get-Location Set-Location ${Site}: #Gather Collection and Parent Folder Information, and set the ObjectType for WMI Queries switch ($CollectionType) { 1 {$CMCollection = Get-CMUserCollection -Name $CollectionName $FolderObjectType = "5001"} 2 {$CMCollection = Get-CMDeviceCollection -Name $CollectionName $FolderObjectType = "5000"} } $CMCollectionID = $CMCollection.CollectionID $FolderObj = (Get-WmiObject -Class SMS_ObjectContainerItem -Namespace Root\SMS\Site_$Site -Filter "InstanceKey='$CMCollectionID' and ObjectType='$FolderObjectType'").ContainerNodeID $FolderName = (Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site -Filter “ContainerNodeID='$FolderObj' and ObjectType='$FolderObjectType'”).Name $ReturnValue = @{ CollectionName = $CMCollection.Name LimitingCollectionName = $CMCollection.LimitToCollectionName ParentFolder = if($FolderName){$FolderName}else{'Root'} Comment = $CMCollection.Comment Site = if($CMCollection){$CMCollection.CollectionID.Substring(0,3)}else{''} CollectionType = if($CMCollection.CollectionType -eq '1'){'User'}elseif($CMCollection.CollectionType -eq '2'){'Device'}else{''} RefreshDays = $CMCollection.RefreshSchedule.DaySpan RefreshType = if($CMCollection.RefreshType -eq '2'){'Periodic'}elseif($CMCollection.RefreshType -eq '4'){'Incremental'}elseif($CMCollection.RefreshType -eq '6'){'Both'}else{''} RefreshStart = $CMCollection.RefreshSchedule.StartTime Ensure = if($CMCollection){'Present'}else{'Absent'} } #Logout Set-Location $OriginalLocation if ($context) { $context.Undo() $context.Dispose() CloseUserToken($newToken) } $ReturnValue } ###################################################################################### # The Set-TargetResource cmdlet. # This function will pass the "apply" switch back to the validate function ###################################################################################### function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String] $CollectionName, [parameter(Mandatory = $true)] [PSCredential] $SCCMAdministratorCredential, [System.String] $LimitingCollectionName, [System.String] $ParentFolder, [System.String] $Comment, [System.String] $Site, [ValidateSet("1","2")] [System.String] $CollectionType, [System.String] $RefreshDays, [ValidateSet("2","4","6")] [System.String] $RefreshType, [System.DateTime] $RefreshStart, [ValidateSet("Present","Absent")] [System.String] $Ensure ) ValidateProperties @PSBoundParameters -Apply } ###################################################################################### # The Test-TargetResource cmdlet. # This function will only return a $true $false on compliance ###################################################################################### function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String] $CollectionName, [parameter(Mandatory = $true)] [PSCredential] $SCCMAdministratorCredential, [System.String] $LimitingCollectionName, [System.String] $ParentFolder, [System.String] $Comment, [System.String] $Site, [ValidateSet("1","2")] [System.String] $CollectionType, [System.String] $RefreshDays, [ValidateSet("2","4","6")] [System.String] $RefreshType, [System.DateTime] $RefreshStart, [ValidateSet("Present","Absent")] [System.String] $Ensure ) ValidateProperties @PSBoundParameters } ###################################################################################### # The ValidateProperties cmdlet. # This function accepts an -apply flag and "does the work" ###################################################################################### function ValidateProperties { param ( [parameter(Mandatory = $true)] [System.String] $CollectionName, [parameter(Mandatory = $true)] [PSCredential] $SCCMAdministratorCredential, [System.String] $LimitingCollectionName, [System.String] $ParentFolder, [System.String] $Comment, [System.String] $Site, [ValidateSet("1","2")] [System.String] $CollectionType = "2", [System.String] $RefreshDays, [ValidateSet("2","4","6")] [System.String] $RefreshType, [System.DateTime] $RefreshStart, [ValidateSet("Present","Absent")] [System.String] $Ensure = "Present", [Switch]$Apply ) #Set initial TestedOK value to true, whch will be called later to see if all variables are still valid [boolean]$TestedOK = $true #Login ($oldToken, $context, $newToken) = ImpersonateAs -cred $SCCMAdministratorCredential #Load Module if missing then set the location for execution if(!(Get-Module ConfigurationManager)) { Try { Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1') } Catch { Throw "Cannot load the SCCM Module, please ensure the SCCM Admin tools are installed and try again" } } $ComputerInfo = Get-WmiObject Win32_ComputerSystem $ComputerFQDN = $ComputerInfo.Name + '.' + $ComputerInfo.Domain $CM12ProviderLocation = Get-WmiObject -Query "Select * From SMS_ProviderLocation where ProviderForLocalSite = True" -Namespace "root\sms" -computername $ComputerFQDN if(!($Site)) { $Site = $CM12ProviderLocation.SiteCode } if(!((Get-PSDrive) -like $Site)) { throw "Problems discovering a valid Site. Please Investigate." } $OriginalLocation = Get-Location Set-Location ${Site}: #Gather the Collection and Parent Collection and set folder variables Switch($CollectionType) { 1 { $CollExist = Get-CMUserCollection -Name $CollectionName If($LimitingCollectionName) {$ParentCollExist = Get-CMUserCollection -Name $LimitingCollectionName} $ConflictExist = Get-CMDeviceCollection -Name $CollectionName $CollectionFolder = $Site + ':\UserCollection\' $FolderType = "5001" } 2 { $CollExist = Get-CMDeviceCollection -Name $CollectionName If($LimitingCollectionName) {$ParentCollExist = Get-CMDeviceCollection -Name $LimitingCollectionName} $ConflictExist = Get-CMUserCollection -Name $CollectionName $CollectionFolder = $Site + ':\DeviceCollection\' $FolderType = "5000" } } If($Ensure -eq 'Absent') { #Delete if Collection exists and Ensure is Absent If($CollExist) { If($Apply) { Switch($CollectionType) { 1 {Remove-CMUserCollection -Name $CollectionName -force} 2 {Remove-CMDeviceCollection -Name $CollectionName -force} } } else { [boolean]$TestedOK = $false } } } Else { #Check for collection and it's parent, make corrections if needed. If(!($ParentCollExist) -and $LimitingCollectionName) { throw "The limiting collection $LimitingCollectionName cannot be found in Site $Site." } If($ConflictExist) { throw "A conflicting collection already exists with the name $CollectionName as another collection type." } #If the Collection Doesn't Exist, create it and recapture the $CollExist variable If(!($CollExist)) { Write-Verbose -Message "Collection $CollectionName cannot be found in Site $Site." If($apply){ Write-Verbose -Message "Creating Collection..." If(!($LimitingCollectionName)) {$LimitingCollectionName = "All Systems"} Switch($CollectionType) { 1 {$CreateCollection = New-CMUserCollection -Name $CollectionName -LimitingCollectionName $LimitingCollectionName} 2 {$CreateCollection = New-CMDeviceCollection -Name $CollectionName -LimitingCollectionName $LimitingCollectionName} } $CollExist = $CreateCollection } else { [boolean]$TestedOK = $false } } #If the Collection STILL doesn't exist (recheck), it's becuase -apply wasn't set and these checks are unnessisary If($CollExist) { #Correct the Limiting Collection Name, if needed if(!($CollExist.LimitToCollectionName -eq $LimitingCollectionName) -and $LimitingCollectionName) { $cur = $CollExist.LimitToCollectionName Write-verbose -Message "Limiting Collection $cur does not match the desired name $LimitingCollectionName." if($Apply) { Write-Verbose -Message "Updating Limiting Collection..." Switch($CollectionType) { 1 {Set-CMUserCollection -Name $CollectionName -LimitingCollectionName $LimitingCollectionName} 2 {Set-CMDeviceCollection -Name $CollectionName -LimitingCollectionName $LimitingCollectionName} } } else { [boolean]$TestedOK = $false } } #Correct the Comment, if needed if(!($CollExist.Comment -eq $Comment) -and $Comment) { Write-verbose -Message "The Comment does not match the desired value." if($Apply) { Write-Verbose -Message "Updating Comments..." Switch($CollectionType) { 1 {Set-CMUserCollection -Name $CollectionName -Comment $Comment} 2 {Set-CMDeviceCollection -Name $CollectionName -Comment $Comment} } } else { [boolean]$TestedOK = $false } } #Correct the RefreshType, if needed if(!($CollExist.RefreshType -eq $RefreshType) -and $RefreshType) { Write-verbose -Message "The Refresh Type is set incorrectly." if($Apply) { Write-Verbose -Message "Updating RefreshType..." $CollExist.RefreshType = $RefreshType $CollExist.put() | Out-Null } else { [boolean]$TestedOK = $false } } #Correct the RefreshDays and generate a startdate, if needed. These are processed together as both variables are related $IntervalClass = [WMIClass]"\\$($ComputerFQDN)\root\SMS\Site_$($Site):SMS_ST_RecurInterval" $Interval = $IntervalClass.CreateInstance() $CollExistID = $CollExist.CollectionID $CollExistWMI = [wmi]"\\$($ComputerFQDN)\root\SMS\Site_$($Site):SMS_Collection.CollectionID='$CollExistID'" if(!($CollExistWMI.RefreshSchedule.DaySpan -eq $RefreshDays) -and $RefreshDays) { Write-verbose -Message "The Refresh Days is set incorrectly." if($Apply) { Write-Verbose -Message "Updating RefreshDays..." $Interval.DaySpan = $RefreshDays if(!($RefreshStart) -and !($CollExistWMI.RefreshSchedule.StartTime)) { Write-Verbose -Message "No Refresh start time has been specified, generating a random date" $DateMin = Get-date -year 2011 -month 1 -day 1 $DateMax = Get-date $NewRefreshStart = New-object DateTime(Get-Random -min $DateMin.ticks -max $DateMax.ticks) } elseif(!($RefreshStart) -and $CollExistWMI.RefreshSchedule.StartTime) { $NewRefreshStart = [System.Management.ManagementDateTimeconverter]::ToDateTime($CollExistWMI.RefreshSchedule.StartTime) } } else { [boolean]$TestedOK = $false } } #If the RefreshDays hasn't been set, set the Interval variable to live variable so it doesn't change during commit if(!($RefreshDays)) {$Interval.DaySpan = $CollExistWMI.RefreshSchedule.DaySpan} #Compare the stored Start Time with the desired start time if($CollExistWMI.RefreshSchedule.StartTime){$CollExistStartTime = [System.Management.ManagementDateTimeconverter]::ToDateTime($CollExistWMI.RefreshSchedule.StartTime)} if((!($CollExistStartTime -eq $RefreshStart) -and $RefreshStart) -or (!($CollExistStartTime -eq $NewRefreshStart) -and $NewRefreshStart)) { if(!($RefreshStart)) {$RefreshStart = $NewRefreshStart} $Date = [System.Management.ManagementDateTimeconverter]::ToDMTFDateTime($RefreshStart.ToString()) if($Apply) { Write-verbose -Message "Updating..." $Interval.StartTime = $Date } else { [boolean]$TestedOK = $false } } #If the RefreshStart hasn't been set, set the Interval variable to live variable so it doesn't change during commit if(!($RefreshDays) -and !($NewRefreshStart)) {$Interval.StartTime = $CollExistWMI.RefreshSchedule.StartTime} #If any interval changes were collected, it's time to apply them if($apply) { $CollExistWMI.RefreshSchedule = $Interval $CollExistWMI.put() | Out-Null } #Move a Collection to the appropriate Folder $CollExistID = $CollExist.CollectionID $CurrFolderID = (Get-WmiObject -Class SMS_ObjectContainerItem -Namespace Root\SMS\Site_$Site -Filter "InstanceKey='$CollExistID' and ObjectType='$FolderType'").ContainerNodeID $CurrFolder = (Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site -Filter “ContainerNodeID='$CurrFolderID' and ObjectType='$FolderType'”).Name if(!($CurrFolderID)) {$CurrFolderID = 0} if(!($CurrFolder)) {$CurrFolder = "Root"} if(!($CurrFolder -eq $ParentFolder) -and $ParentFolder){ Write-Verbose -Message "The collection was not found in $ParentFolder." if($apply) { Write-Verbose -Message "Moving..." $ParentFolderID = (Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $ParentFolder -and $_.ObjectType -eq $FolderType}).ContainerNodeID If(!($ParentFolderID)) {$ParentFolderID ="0"} $ContainerItem = [WMIClass]"\\$ComputerFQDN\root\SMS\Site_${Site}:SMS_ObjectContainerItem" $MoveItem = $ContainerItem.PSBase.GetMethodParameters("MoveMembers") $MoveItem.ContainerNodeID = $CurrFolderID $MoveItem.InstanceKeys = $CollExistID $MoveItem.ObjectType = $FolderType $MoveItem.TargetContainerNodeID = $ParentFolderID $Result = $ContainerItem.PSBase.InvokeMethod("MoveMembers",$MoveItem,$null) Write-Verbose -Message "Moved collection $CollExistID to folder $ParentFolder" } else { [boolean]$TestedOK = $false } } } } #Logout Set-Location $OriginalLocation if ($context) { $context.Undo() $context.Dispose() CloseUserToken($newToken) } #If this is only a test, return the results if(!($apply)){ return $TestedOK } } ###################################################################################### # The below functions are used for user impersonation # There are 3 functions in total ###################################################################################### function Get-ImpersonatetLib { if ($script:ImpersonateLib) { return $script:ImpersonateLib } $sig = @' [DllImport("advapi32.dll", SetLastError = true)] public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll")] public static extern Boolean CloseHandle(IntPtr hObject); '@ $script:ImpersonateLib = Add-Type -PassThru -Namespace 'Lib.Impersonation' -Name ImpersonationLib -MemberDefinition $sig return $script:ImpersonateLib } function ImpersonateAs([PSCredential] $cred) { [IntPtr] $userToken = [Security.Principal.WindowsIdentity]::GetCurrent().Token $userToken $ImpersonateLib = Get-ImpersonatetLib $bLogin = $ImpersonateLib::LogonUser($cred.GetNetworkCredential().UserName, $cred.GetNetworkCredential().Domain, $cred.GetNetworkCredential().Password, 9, 0, [ref]$userToken) if ($bLogin) { $Identity = New-Object Security.Principal.WindowsIdentity $userToken $context = $Identity.Impersonate() } else { throw "Can't Logon as User $cred.GetNetworkCredential().UserName." } $context, $userToken } function CloseUserToken([IntPtr] $token) { $ImpersonateLib = Get-ImpersonatetLib $bLogin = $ImpersonateLib::CloseHandle($token) if (!$bLogin) { throw "Can't close token" } } # FUNCTIONS TO BE EXPORTED Export-ModuleMember -Function *-TargetResource |