CCM.psm1

$sbCCMGetCimParm = {
     try {
         $Global:CCMConnection.PSObject.Copy()
     }
     catch {
         Throw 'Not connected to CCM, reconnect using Connect-CCM'
     }
 }

 #region Force confirm prompt for Remove-CimInstance
 <#
 I think this is bad practice, but I don't have a good workaround - Remove-CimInstance can delete any CCM objects
 piped to it. Users can override this, but this will make it a bit harder to accidentally remove collections, resources, etc.
 #>

 try {
      $PSDefaultParameterValues.Add("Remove-CimInstance:Confirm",$true)
 }
 catch{}
#end region Force confirm prompt

#using Add-Type instead of Enum because I want to group by namespace
Add-Type -TypeDefinition @'
namespace CCM
{
     public enum Month
     {
          January = 1,
          February = 2,
          March = 3,
          April = 4,
          May = 5,
          June = 6,
          July = 7,
          August = 8,
          September = 9,
          October = 10,
          November = 11,
          December = 12
 
     }
     public enum FolderType
     {
          TYPE_PACKAGE = 2,
          TYPE_ADVERTISEMENT = 3,
          TYPE_QUERY = 7,
          TYPE_REPORT = 8,
          TYPE_METEREDPRODUCTRULE = 9,
          TYPE_CONFIGURATIONITEM = 11,
          TYPE_OSINSTALLPACKAGE = 14,
          TYPE_STATEMIGRATION = 17,
          TYPE_IMAGEPACKAGE = 18,
          TYPE_BOOTIMAGEPACKAGE = 19,
          TYPE_TASKSEQUENCEPACKAGE = 20,
          TYPE_DEVICESETTINGPACKAGE = 21,
          TYPE_DRIVERPACKAGE = 23,
          TYPE_DRIVER = 25,
          TYPE_SOFTWAREUPDATE = 1011,
          TYPE_CONFIGURATIONBASELINE = 2011
     }
     public enum RecurrenceType
     {
          NONE = 1,
          DAILY = 2,
          WEEKLY = 3,
          MONTHLYBYWEEKDAY = 4,
          MONTHLYBYDATE = 5
     }
 
     public enum ServiceWindowType
     {
          GENERAL = 1,
          UPDATES = 4,
          OSD = 5
     }
     public enum CollectionType
     {
          OTHER = 0,
          USER = 1,
          DEVICE = 2
     }
     public enum RefreshType
     {
          Manual = 1,
          Periodi = 2,
          Incremental = 4,
          IncrementalAndPeriodic = 6
     }
     public enum EvalResult {
          Not_Yet_Evaluated = 1,
          Not_Applicable = 2,
          Evaluation_Failed = 3,
          Evaluated_Remediated_Failed = 4,
          Not_Evaluated_Dependency_Failed = 5,
          Evaluated_Remediated_Succeeded = 6,
          Evaluation_Succeeded = 7
     }
}
'@

#helper function for adding typenames
<#
some objects with lazy properties use Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance
this will add the full object classname to the top of PSObject.TypeNames
#>

Filter Add-CCMClassType { $PSItem.PSObject.TypeNames.Insert(0,"Microsoft.Management.Infrastructure.CimInstance#$($PSItem.CimClass.CimClassName)");$PSItem }
Function Add-CCMMembershipDirect {
    [cmdletbinding(SupportsShouldProcess = $true)]

    param(
        [Parameter()]
        [CimInstance[]]$Resource,

        [Parameter()]
        [CimInstance]$Collection
    )

    Begin {      
        $CimSession = Get-CimSession -InstanceId $Collection.GetCimSessionInstanceId()
        $cimHash = $Global:CCMConnection.PSObject.Copy()
    }

    Process {

        ForEach ($obj in $Resource) {
            $null = New-CimInstance -Namespace $cimHash.Namespace -ErrorAction Stop -OutVariable +cmRule -ClassName SMS_CollectionRuleDirect -ClientOnly -Property @{ 
                ResourceClassName = 'SMS_R_System'
                RuleName          = '{0} added by {1} via {2} from {3} on {4}' -f $obj.Name, $env:USERNAME, $PSCmdlet.MyInvocation.InvocationName, $CimSession.ComputerName.ToUpper(), (Get-Date -Format 'MM/dd/yyyy hh:mm:ss tt')
                ResourceID        = $obj.ResourceID
            } 
        } 

    }

    End {        
        $Collection | Invoke-CimMethod -MethodName AddMemberShipRules -Arguments @{ CollectionRules = [CimInstance[]]$cmRule } -ErrorAction Stop

        $cmRule | Out-String | Write-Verbose
    }

}
<# Testing with this was unsuccessful, keeping in module for reference
 
Function Add-CCMMembershipQuery
{
    [cmdletbinding(SupportsShouldProcess=$true)]
 
    param(
 
        #[CimSession]$CimSession,
 
        [String[]]$ResourceName,
 
        [Parameter(Mandatory=$true)]
        $Collection,
 
        [DateTime]$ExpriationDate = (Get-Date).AddDays(-1),
        [CimSession]$CimSession = (Get-CimSession -Name 'ccm-*' | Select-Object -First 1)
 
    )
 
    Begin
    {
 
        $cimHash = $Global:CCMConnection.PSObject.Copy()
 
        $QueryExpression = @'
select
    SMS_R_SYSTEM.ResourceID,
    SMS_R_SYSTEM.ResourceType,
    SMS_R_SYSTEM.Name,
    SMS_R_SYSTEM.SMSUniqueIdentifier,
    SMS_R_SYSTEM.ResourceDomainORWorkgroup,
    SMS_R_SYSTEM.Client from SMS_R_System
 
inner join SMS_G_System_SYSTEM on SMS_G_System_SYSTEM.ResourceID = SMS_R_System.ResourceId
 
where
    SMS_G_System_SYSTEM.Name = "{0}"
 
and
    (DateDiff(hh, SMS_R_System.CreationDate, GetDate()) < 12)
 
'@
 
    }
 
    Process
    {
 
        ForEach ($obj in $ResourceName)
        {
         
            $null = New-CimInstance -Namespace $cimHash.Namespace -OutVariable +cmRule -ClassName SMS_CollectionRuleQuery -ClientOnly -Property @{
                RuleName = 'RBBuilds| {0} | {1} added by {2}' -f $ExprirationDate, $obj, $env:USERNAME, $PSCmdlet.MyInvocation.InvocationName, $CimSession.ComputerName.ToUpper(), (Get-Date -Format 'MM/dd/yyyy hh:mm:ss tt')
                QueryExpression = $QueryExpression -f $ResourceName
            }
 
        }
 
    }
 
    End
    {
        #$cmRule
        #$SmsCollection
        $Collection | Invoke-CimMethod -MethodName AddMembershipRules -Arguments @{ CollectionRules = [CimInstance[]]$cmRule }
    }
 
}
 
#>

function Connect-CCM {
 <#
    .SYNOPSIS
        This function establishes a connection to an SCCM Server system.
 
    .DESCRIPTION
        This function establishes a connection to an SCCM Server system. The function creates
        a CIM session [CimSession], queries the server for the SCCM site and namespace, and
        stores connection information in a global variable
 
    .INPUTS
        [string]
 
    .OUTPUTS
        [CimInstance]
 
    .EXAMPLE
        C:\PS>Connect-CCM -Server WINSCCM01
 
        Connects to the SCCM server WINSCCM01
#>

    [CmdletBinding()]
    Param
    (
        #The name of the SCCM server
        [Parameter(Mandatory = $true)]
        [string]$ComputerName,

        #Removes previous CimSession if found
        [Parameter()]
        [switch]$Reconnect,

        #Specifies a PSCredential object that contains credentials for authenticating with the server
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    process {
        Write-Verbose "Looking for CIM Session 'ccmConnection'"
        $cimSession = Get-CimSession -Name "ccmConnection" -ErrorAction SilentlyContinue | Select-Object -First 1

        if ($Reconnect) {
            $cimSession | Remove-CimSession
        }
        
        $siteParm = @{
            ClassName = 'SMS_ProviderLocation'
            NameSpace = 'root/sms'
        }

        $siteName = try {
            (Get-CimInstance @siteParm -CimSession $cimSession)[0].NamespacePath -replace '^.+site_'
        }
        catch {
            $cimSession = New-CimSession -ComputerName $ComputerName -Name "ccmConnection" -Credential $Credential
            (Get-CimInstance @siteParm -CimSession $cimSession)[0].NamespacePath -replace '^.+site_'
        }
    }
    end {
        Set-Variable -Name global:CCMConnection -Value @{
            CimSession = $cimSession
            NameSpace  = 'root\sms\site_{0}' -f $siteName
        }
    }
    
}
Function Find-CCMObject {
    [Alias()]
    [cmdletbinding()]

    param(
        #Specifies a CIM instance object to use as input.
        [Parameter(ValueFromPipeline, Mandatory)]
        [ciminstance[]]$inputObject
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }

        $cimHash['ClassName'] = 'SMS_ObjectContainerItem'
    }

    Process {
        foreach ($a_inputObject in $inputObject){
            if ($a_inputObject.CimClass.CimClassName -ne 'SMS_ObjectContainerNode'){
                $keyProperty = $a_inputObject.CimClass.CimClassProperties.Where({$_.Qualifiers.Name -eq 'key' -or $_.Name -match 'uniqueid$'}) |
                    Sort-Object { $PSItem.name -match 'uniqueid'} |
                        Select-Object -Last 1
                $findParm = @{
                    #the uniqueID for the app includes version number, but the container location does not
                    Filter =  '(InstanceKey = "{0}")' -f ($a_inputObject.($keyProperty.Name) -replace '/\d{1,5}$')
                }      

                $containerItem = Get-CimInstance @cimHash @findParm
                $currentContainerNode = Get-CCMObjectContainerNode -Identity $containerItem.ContainerNodeID
            }
            else{
                $currentContainerNode = $a_inputObject
            }

            $sb = [System.Text.StringBuilder]::new()
            $null = $sb.Append("\$($currentContainerNode.Name)")

            while($currentContainerNode.ParentContainerNodeID){
                Write-Verbose $sb.ToString()
                $currentContainerNode = Get-CCMObjectContainerNode -Identity $currentContainerNode.ParentContainerNodeID
                $null = $sb.Insert(0,"\$($currentContainerNode.Name)")                
            }
            $sb.ToString()
        }
    }
}
Function Get-CCMApplication {
    [Alias('Get-SMS_Application')]
    [cmdletbinding(DefaultParameterSetName = 'inputObject')]

    param(
        #Specifies an SCCM Application object by providing the CI_ID, CI_UniqueID, or 'LocalizedDisplayName'.
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Identity')]
        [Alias('CI_ID', 'CI_UniqueID', 'Name', 'LocalizedDisplayName')]
        [String[]]$Identity,

        #Specifies a CIM instance object to use as input.
        [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'inputObject')]
        [ciminstance]$inputObject,

        #Specifies a where clause to use as a filter. Specify the clause in either the WQL or the CQL query language.
        [Parameter(ParameterSetName = 'Filter')]
        [string]$Filter
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }

        $cimHash['ClassName'] = 'SMS_ApplicationLatest'

        $identityFilter = 'LocalizedDisplayName LIKE "{0}" OR CI_UniqueID LIKE "{0}"'
    }

    Process {
        Write-Debug "Choosing parameterset: '$($PSCmdlet.ParameterSetName)'"
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                switch -Regex ($Identity -replace '\*','%') {
                    '^(\d|%)+$' {
                        Get-CimInstance @cimHash -Filter ('CI_ID LIKE "{0}"' -f $PSItem)
                    }
                    default {
                        Get-CimInstance @cimHash -filter ($identityFilter -f $PSItem)
                    }
                }
            }
            'inputObject' {
                $inputObject | Get-CimInstance
            }
            'Filter' {
                Foreach ($obj in $Filter) {
                    Get-CimInstance @cimHash -filter $Filter
                }
            }
        }

    }
}
Function Get-CCMCimClass {   
    [Alias('Get-CCMClass')]
    [cmdletbinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [Alias('Class')]
        [string]$ClassName
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }       
    }

    Process {
        Get-CimClass @cimHash @PSBoundParameters
    }
}
Function Get-CCMCimInstance {   
    [Alias('Get-CCMInstance')]
    [cmdletbinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [Alias('Class')]
        [string]$ClassName,

        [Parameter()]
        [string]$Filter,

        [Parameter(Position = 1)]
        [Alias('Properties')]
        [string[]]$Property
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }        
    }

    Process {
        Get-CimInstance @cimHash @PSBoundParameters
    }
}
function Get-CCMClientExecutionRequest {
    param (
        
        [Parameter(ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName',
            Position = 0,
            Mandatory = $true)]
        [string]$ComputerName,

        [Parameter(ParameterSetName = 'ComputerName')]
        [PSCredential]$Credential 

    )

    Begin
    {}

    Process {
        if (-not $CimSession) {
        
            try {
                $CimSession = Get-CimSession -ComputerName $ComputerName -ErrorAction Stop
            }
            catch {
                
                $cimParm = @{
                    ComputerName = $ComputerName
                }
                if ($Credential) {
                    $cimParm['Credential'] = $Credential
                }

                $CimSession = New-CimSession @cimParm -ErrorAction Stop
            }
            
        }
        
        $cimParm = @{            
            OutVariable = 'update'
            NameSpace   = 'root\ccm\SoftMgmtAgent'
            ClassName   = 'CCM_ExecutionRequestEx'
            CimSession  = $CimSession
        }

        Get-CimInstance @cimParm | ForEach-Object { $PSItem.PSObject.TypeNames.Insert(0, 'Microsoft.Management.Infrastructure.CimInstance.CCM_ExecutionRequestEx') ; $PSItem }
        
    }
}
Function Get-CCMCollection {

<#
.SYNOPSIS
 
Get an SCCM Collection
 
.DESCRIPTION
 
Get an SCCM Collection by Name or CollectionID
 
.PARAMETER Name
Specifies the file name.
 
.PARAMETER Extension
Specifies the extension. "Txt" is the default.
 
.INPUTS
 
None. You cannot pipe objects to Add-Extension.
 
.OUTPUTS
 
System.String. Add-Extension returns a string with the extension
or file name.
 
.EXAMPLE
C:\PS> Get-CCMCollection *
Retrieves all collections
 
.EXAMPLE
C:\PS> Get-CCMCollection *SVR*
Returns all collections with SVR in the name
 
.EXAMPLE
C:\PS> Get-CCMCollection *SVR* -HasMaintenanceWindow
Returns all collections with SVR in the name that have maintenance windows
 
.LINK
 
https://github.com/saladproblems/CCM-Core
 
#>

    [Alias('Get-SMS_Collection')]
    [cmdletbinding(DefaultParameterSetName = 'inputObject')]

    param(
        #Specifies a CIM instance object to use as input.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
        [ciminstance[]]$InputObject,

        #Specifies an SCCM collection object by providing the collection name or ID.
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Identity')]
        [Alias('ClientName', 'CollectionName', 'CollectionID', 'Name')]
        [string[]]$Identity,

        #Specifies a where clause to use as a filter. Specify the clause in the WQL query language.
        [Parameter(Mandatory, ParameterSetName = 'Filter')]
        [string]$Filter,

        #Only return collections with service windows - Maintenance windows are a lazy property, requery to view maintenance window info
        [Parameter()]
        [alias('HasServiceWindow')]
        [switch]$HasMaintenanceWindow,

        #Specifies a set of instance properties to retrieve.
        [Parameter()]
        [string[]]$Property

    )

    Begin {
        $cimHash = $Global:CCMConnection.PSObject.Copy()

        if ($Property) {
            $cimHash['Property'] = $Property
        }

        if ($HasMaintenanceWindow.IsPresent) {
            $HasMaintenanceWindowSuffix = ' AND (ServiceWindowsCount > 0)'
        }
    }

    Process {
        Write-Debug "Chose parameterset '$($PSCmdlet.ParameterSetName)'"
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                $cimFilter = switch -Regex ($Identity) {
                    '\*' {
                        'Name LIKE "{0}" OR CollectionID LIKE "{0}"' -f ($PSItem -replace '\*', '%')
                    }

                    Default {
                        'Name = "{0}" OR CollectionID = "{0}"' -f $PSItem
                    }
                }
            }
            'Filter' {
                Get-CimInstance @cimHash -ClassName SMS_Collection -Filter $Filter
            }
            'InputObject' {
                switch ($InputObject) {
                    {$PSItem.CimInstance.cimclass -match 'SMS_ObjectContainerItem'} {
                        'CollectionID = "{0}"' -f $PSItem.CollectionID
                    }
                }
            }
        }

        if ($cimFilter) {
            $cimFilter = '({0}){1}' -f ($cimFilter -join ' OR '), $HasMaintenanceWindowSuffix
            Get-CimInstance @cimHash -ClassName SMS_Collection -Filter $cimFilter |
                Add-CCMClassType
        }
    }
    End
    {}
}
Function Get-CCMCollectionMember {    
    
    [cmdletbinding()]
    param(
        #Specifies an SCCM Resource object by providing the 'Name' or 'ResourceID'.
        [Parameter(ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Identity')]
        [Alias('CollectionName','CollectionID')]
        [WildcardPattern]$Identity,

        #Specifies a CIM instance object to use as input.
        [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'inputObject')]
        [ciminstance[]]$inputObject,

        #Specifies a where clause to use as a filter. Specify the clause in either the WQL or the CQL query language.
        [Parameter(ParameterSetName = 'Filter')]
        [string]$Filter
    )

    Begin
    {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }
        #$cimHash['ClassName'] = 'SMS_FullCollectionMembership'

        $identityFilter = 'CollectionID LIKE "{0}" OR Name LIKE "{0}"'

        $collParm = @{
            KeyOnly = $true
            ClassName = 'SMS_Collection'
        }
    }

    Process {
        Write-Debug $PSCmdlet.ParameterSetName
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                foreach($collection in (Get-CimInstance @cimHash @collParm -filter ($identityFilter -f $Identity.ToWql()) )) {
                    Get-CimInstance @cimHash -ClassName SMS_FullCollectionMembership -Filter ($identityFilter -f $collection.CollectionID)
                }             
            }
            'inputObject' {
                foreach ($a_inputObject in $inputObject) {
                    Get-CimInstance @cimHash -ClassName SMS_FullCollectionMembership -Filter "CollectionID = '$($a_inputObject.CollectionID)'"
                }
            }
            'Filter' {
                Get-CimInstance @cimHash -filter $Filter
            }
        }     
    }
}
Function Get-CCMCollectionSettings {    
    [cmdletbinding()]

    param(
        [ValidateScript( {$PSItem.CimClass.CimClassName -eq 'SMS_Collection'})]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Name')]
        [CimInstance]$Collection        
    )

    Begin
    {}

    Process {
        foreach ($a_Collection in $Collection) {
            $cimHash = @{
                NameSpace  = $a_Collection.CimSystemProperties.Namespace
                CimSession = Get-CimSession -InstanceId $a_Collection.GetCimSessionInstanceId()
            }

            Get-CimInstance @cimHash -ClassName SMS_CollectionSettings -Filter "CollectionID = '$($a_Collection.CollectionID)'" | Get-CimInstance
        }
           
    }

}
Function Get-CCMObjectContainerItem {

    [Alias('Get-SMS_ObjectContainerItem', 'Get-CCMFolderChildItem','gcmfci')]
    [cmdletbinding(DefaultParameterSetName = 'none')]

    param(
        #Specifies a container by ContainerNodeID or SMS_ObjectContainerItem.
        [Parameter(ValueFromPipeline, Position = 0, ParameterSetName = 'Identity')]
        [string[]]$Identity,

        #Specifies a where clause to use as a filter. Specify the clause in either the WQL or the CQL query language.
        [Parameter(Mandatory = $true, ParameterSetName = 'Filter')]
        [string]$Filter,

        #Specifies a set of instance properties to retrieve.
        [Parameter()]
        [string[]]$Property
    )

    Begin {
        $cimHash = $Global:CCMConnection.PSObject.Copy()

        $cimHash['ClassName'] = 'SMS_ObjectContainerItem'
        $cimHash['KeyOnly'] = $true
    }

    Process {
        Write-Debug "Chose ParameterSet $($PSCmdlet.ParameterSetName)"
        $result = Switch -Regex ($PSCmdlet.ParameterSetName) {
            'none' {
                Get-CimInstance @cimHash
            }
            'Identity' {
                switch -Regex ($Identity) {
                    '^SMS_ObjectContainerNode'{
                        Get-CimInstance  @cimHash -Filter ($PSItem -replace '^.+?\s')
                        continue     
                    }
                    '^(\d|\*)+$' {
                        Get-CimInstance @cimHash -Filter ('ContainerNodeID LIKE "{0}"' -f $PSItem -replace '\*', '%' )
                    }
                    default {
                        $PSItem | Write-Warning
                    }
                }
            }
            'Filter' {
                Get-CimInstance @cimHash -Filter $Filter
            }
        }        

        if ($result){
            $resultParm = @{
                CimSession = $cimHash.CimSession
                NameSpace = $cimHash.NameSpace
                ClassName = ($result | Select-Object -first 1).ObjectTypeName -replace '^([^_]*_[^_]*).*$','$1'
            }

            #this will fail on types with multiple keys, may need to add support if any of these types can be in a folder
            $resultKey = (Get-CimClass @resultParm).CimClassProperties |
                Where-Object {$PSItem.Qualifiers.Name -eq 'key' -or $PSItem.Name -match 'uniqueid$'} |
                    Select-Object -ExpandProperty Name -First 1
            
            $resultFilter = '({0} LIKE "{1}%")' #testing to see if this gets applications - they have a "/<version>" suffix

            if ($Property) {
                $resultParm['Property'] = $Property
            }   
            foreach ($a_result in $result){
                Get-CimInstance @resultParm -Filter ($resultFilter -f $resultKey,$a_result.InstanceKey)
            }
        }
        else{
            Write-Verbose "No childitems found in '$Identity'"
        }
    }
    End
    {}
}
Function Get-CCMObjectContainerNode {

    [Alias('Get-SMS_ObjectContainerNode', 'Get-CCMFolder','Get-ObjectContainerNode')]
    [cmdletbinding(DefaultParameterSetName = 'Identity')]

    param(
        #Specifies a container by ContainerNodeID, FolderGuid, or Name
        [Parameter(Mandatory, ValueFromPipeline, Position = 0, ParameterSetName = 'Identity')]
        [string[]]$Identity,

        #Specifies a where clause to use as a filter. Specify the clause in either the WQL or the CQL query language.
        [Parameter(Mandatory = $true, ParameterSetName = 'Filter')]
        [string]$Filter,

        #Specifies a set of instance properties to retrieve.
        [Parameter()]
        [string[]]$Property,

        [Parameter(ValueFromPipeline, ParameterSetName = 'CimInstance')]
        [ciminstance[]]$CimInstance
    )

    Begin {
        $cimHash = $Global:CCMConnection.PSObject.Copy()

        $cimHash['ClassName'] = 'SMS_ObjectContainerNode'

        if ($Property) {
            $cimHash['Property'] = $Property
        }      
    }

    Process {
        Write-Debug "Chose ParameterSet $($PSCmdlet.ParameterSetName)"
        Switch -Regex ($PSCmdlet.ParameterSetName) {
            'none' {
                Get-CimInstance @cimHash
            }
            'Identity' {
                switch -Regex ($Identity) {
                    '^(%|\d).+$' {
                        Get-CimInstance @cimHash -Filter ('ContainerNodeID LIKE "{0}"' -f ($PSItem -replace '\*', '%' ))
                    }
                    default {                        
                        Get-CimInstance @cimHash -Filter ('FolderGuid LIKE "{0}" OR Name LIKE "{0}"' -f ($PSItem -replace '\*', '%' ))
                    }
                }
            }
            'Filter' {
                Get-CimInstance @cimHash -Filter $Filter
            }
            'CimInstance' {
                switch ($CimInstance)
                {
                    {$PSItem.CimClass.CimClassName -eq 'SMS_ObjectContainerNode'} {
                        $CimInstance | Get-CimInstance
                        continue
                    }
                    {$PSItem.CimClass.CimClassName -eq 'SMS_ObjectContainerItem'} {
                        Get-CimInstance @cimHash -Filter ('ContainerNodeID = "{0}"' -f $PSItem.ContainerNodeID)
                        continue
                    }
                    Default {
                        <#
                        $Filter = switch ($PSItem.)
                        Get-CimInstance -CimSession $cimHash.CimSession -Namespace $cimHash.Namespace -ClassName SMS_ObjectContainerItem -Filter ''
                        #>

                    }
                }

            }
        }
    }
    End
    {}
}
Function Get-CCMResource {
<#
.SYNOPSIS
 
Get an SCCM Resource
 
.DESCRIPTION
 
Get an SCCM Resource by Name or ResourceID
 
.OUTPUTS
Microsoft.Management.Infrastructure.CimInstance#root/sms/site_qtc/SMS_R_System
 
.EXAMPLE
C:\PS> Get-CCMResource *
Retrieves all Resources
 
.EXAMPLE
C:\PS> Get-CCMResource *SVR*
Returns all resources with SVR in the name
 
.LINK
 
https://github.com/saladproblems/CCM-Core
 
#>

    [Alias('Get-SMS_R_System')]
    [cmdletbinding(DefaultParameterSetName = 'inputObject')]

    param(
        #Specifies an SCCM Resource object by providing the 'Name' or 'ResourceID'.
        [Parameter(ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Identity')]
        [Alias('Name','ClientName','ResourceName''ResourceID')]
        [string[]]$Identity,

        #Specifies a CIM instance object to use as input.
        [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'inputObject')]
        [ciminstance]$inputObject,

        #Specifies a where clause to use as a filter. Specify the clause in either the WQL or the CQL query language.
        [Parameter(ParameterSetName = 'Filter')]
        [string]$Filter
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }
        $cimHash['ClassName'] = 'SMS_R_System'        
    }

    Process {
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                switch -Regex ($Identity)  {
                   '^(%|\d).+$' {
                        Get-CimInstance @cimHash -Filter ('ResourceID LIKE "{0}"' -f $PSItem -replace '\*','%')
                    }
                    default {
                        Get-CimInstance @cimHash -filter ('Name LIKE "{0}"' -f $PSItem -replace '\*','%')
                    }
                }
            }
            'inputObject' {
                $inputObject | Get-CimInstance
            }
            'Filter' {
                Foreach ($obj in $Filter) {
                    Get-CimInstance @cimHash -filter $Filter
                }
            }
        }
           
    }
}
Function Get-CCMResourceMembership {
    [Alias('Get-SMS_FullCollectionMembership')]
    [cmdletbinding(DefaultParameterSetName = 'inputObject')]

    param(
        #Specifies an the members an SCCM resource is a member of by the resource's name or ID.
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Identity')]
        [Alias('ClientName', 'ResourceName', 'ResourceID', 'Name')]
        [string[]]$Identity,
        
        #Specifies a CIM instance object to use as input, must be SMS_R_System (returned by "get-CCMResource")
        [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'inputObject')]
        [ValidateScript( {$PSItem.CimClass.CimClassName -match 'SMS_R_System|SMS_FullCollectionMembership'})]
        [ciminstance]$inputObject,

        #Restrict results to only collections with a ServiceWindow count greater than 0
        [Parameter()]
        [alias('HasServiceWinow')]
        [switch]$HasMaintenanceWindow,

        #Specifies a set of instance properties to retrieve.
        [Parameter()]
        [string[]]$Property,

        # Parameter help description
        [Parameter()]
        [switch]$ShowResourceName
    )

    Begin {     
        $cimHash = $Global:CCMConnection.PSObject.Copy()

        $cimHash['ClassName'] = 'SMS_FullCollectionMembership'

        if ($Property) { $cimHash['Property'] = $Property }

        $getCollParm = @{ HasMaintenanceWindow = $HasMaintenanceWindow.IsPresent }

        if ($Property) {
            $getCollParm['Property'] = $Property
        }
    }

    Process {
        Write-Debug "Choosing parameterset: '$($PSCmdlet.ParameterSetName)'"
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                $resourceMembership = switch -Regex ($Identity) {
                    '^(\d|%)+$' {
                        Get-CimInstance @cimHash -Filter ('ResourceID LIKE "{0}"' -f $PSItem -replace '\*','%')
                    }
                    default {
                        Get-CimInstance @cimHash -filter ('Name LIKE "{0}"' -f $PSItem -replace '\*','%')
                    }
                }
                if ($ShowResourceName.IsPresent) {
                    Write-Host "Collection memberships for: '$($resourceMembership[0].Name)'" -ForegroundColor Green
                }
                Get-CCMCollection -Identity $resourceMembership.CollectionID @getCollParm |
                    Write-Output
            }
            'inputObject' {
                if ($ShowResourceName.IsPresent) {
                    Write-Host "Collection memberships for '$($inputObject.ResourceID)':" -ForegroundColor Green
                }
                $inputObject.ResourceID | Get-CCMResourceMembership @getCollParm
            }
        }   
    }
}
Function Get-CCMScript {
    
    [cmdletbinding(DefaultParameterSetName = 'inputObject')]

    param(
        #Specifies a CIM instance object to use as input.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
        [ciminstance[]]$InputObject,

        #Specifies an SCCM collection object by providing the collection name or ID.
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Identity')]
        [Alias('ScriptGUID', 'ScriptName')]
        [string[]]$Identity,       

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Author')]
        [string[]]$Author,

        [Parameter(ParameterSetName = 'Filter')]
        [string]$Filter
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }        
        $cimHash['ClassName'] = 'SMS_Scripts'             
    }

    Process {
        Write-Debug $PSCmdlet.ParameterSetName
        Switch ($PSCmdlet.ParameterSetName) {
            'inputObject' {
                $inputObject | Get-CimInstance
            }
            'Identity' {
                Foreach ($obj in $Identity) {
                    Get-CimInstance @cimHash -Filter ('ScriptName LIKE "{0}" OR ScriptGUID LIKE "{0}"' -f $obj -replace '\*', '%' -replace '\[', '[$0]' )
                }
            }
            'Author' {
                Foreach ($obj in $Author) {
                    Get-CimInstance @cimHash -Filter "Author LIKE '$($obj -replace '\*','%')'"
                }
            }
            'Filter' {
                Foreach ($obj in $Filter) {
                    Get-CimInstance @cimHash -Filter $Filter
                }
            }           
        }
    }
}
Function Get-CCMScriptExecutionStatus {
    [cmdletbinding()]

    [alias('Get-CCMScriptsExecutionStatus')]
    
    param(
        [Parameter(ValueFromPipeline = $true)]
        [ciminstance[]]
        $Script,

        [Parameter()]
        [datetime]
        $Start,

        [Parameter()]
        [datetime]
        $End

    )

    begin {
        try {
            [hashtable]$cimHash = $Global:CCMConnection.PSObject.Copy()   
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }

        $cimHash['ClassName'] = 'SMS_ScriptsExecutionStatus'

        $cimArray = [System.Collections.ArrayList]::new()
    }

    process {
        $cimArray.AddRange([ciminstance[]]$Script)
    }

    end {
        $filter = $cimArray.ForEach( { 'ScriptGUID = "{0}"' -f $PSItem.ScriptGuid }) -join ' OR '
        
        Get-CimInstance @cimHash -Filter $filter
    }
}
Function Get-CCMUserMachineRelationship {
    [alias('Get-SMS_UserMachineRelationship')]
    [cmdletbinding()]

    param(

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Identity')]
        [alias('Name')]
        [string[]]$Identity,

        [Parameter(ParameterSetName = 'Filter')]
        [string]$Filter,
        
        [Parameter()]        
        [switch]$Active
    )

    Begin {
        try {
            $cimHash = $Global:CCMConnection.PSObject.Copy()   
            $cimHash['ClassName'] = 'SMS_UserMachineRelationship'
        }
        catch {
            Throw 'Not connected to CCM, reconnect using Connect-CCM'
        }

        $filterSuffix = if ($Active.IsPresent) {
            'AND (IsActive = {0})' -f [int]($Active.IsPresent)
        }
    } 

    Process {
        Switch ($PSCmdlet.ParameterSetName) {
            'Identity' {
                Foreach ($obj in $Identity) {
                    Write-Verbose $obj
                    $filter = if ($obj -match '\*') {
                        "ResourceName LIKE '{0}' OR UniqueUserName LIKE '{0}' $filterSuffix" -f ($obj -replace '\*', '%' -replace '\\', '\\')
                    }
                    else {
                        "ResourceName = '{0}' OR UniqueUserName = '{0}' $filterSuffix" -f ($obj -replace '\\', '\\')
                    }
                    Get-CimInstance @cimHash -filter $Filter
                }
                continue
            }
            'Filter' {
                Get-CimInstance @cimHash -filter $Filter
            }
        }           
    }
}
Function Get-NthWeekDay {
    [cmdletbinding()]
    [alias('Get-PatchTuesday')]
    param(
        [parameter()]
        [ccm.Month]$Month = (Get-Date -Format 'MM'),

        [parameter()]
        [int]$Year = (Get-Date -Format 'yyyy'),

        [Parameter()]
        [int]$Nth = 2,

        [Parameter()]
        [DayOfWeek]$DayOfWeek = [DayOfWeek]::Tuesday
    )

    begin {
        $FirstDayOfMonth = ([datetime]"$Month/1/$Year").Date
        $daysInMonth = (($FirstDayOfMonth.AddMonths(1) - $FirstDayOfMonth).totaldays)        
    }
    process {
        
        $foundDays = 1..$daysInMonth | 
            ForEach-Object { $FirstDayOfMonth.AddDays($PSItem - 1) } |
            Where-Object { $PSItem.DayOfWeek -eq $DayOfWeek }
        
        try {
            $foundDays[($Nth - 1)]
        }
        catch {
            Write-Error -ErrorAction Stop -Message "Did not find '$Nth'th '$DayOfWeek' in '$Month', '$Year'"
        }
    }
}
<# This function should be moved to the CCM client module
Function Invoke-CCMClientScheduleUpdate
{
    [cmdletbinding()]
 
    param(
        [string]$ComputerName,
        [pscredential]$Credential,
        [switch]$UseDCOM
    )
 
    Begin
    {
        $x = 0
 
        $taskList = @'
            GUID,Task
            {00000000-0000-0000-0000-000000000003},Discovery Data Collection Cycle
            {00000000-0000-0000-0000-000000000001},Hardware Inventory Cycle
            {00000000-0000-0000-0000-000000000002},Software Inventory Cycle
            {00000000-0000-0000-0000-000000000021},Machine Policy Retrieval Cycle
            {00000000-0000-0000-0000-000000000022},Machine Policy Evaluation Cycle
            {00000000-0000-0000-0000-000000000108},Software Updates Assignments Evaluation Cycle
            {00000000-0000-0000-0000-000000000113},Software Update Scan Cycle
            {00000000-0000-0000-0000-000000000110},DCM policy
 
'@ | ConvertFrom-Csv
         
    }
 
 
    Process
    {
         
        foreach ($aComputerName in $ComputerName)
        {
            $cimParm = @{
                ComputerName = $aComputerName
                ErrorAction = 'Stop'
            }
 
            if ($Credential){ $cimParm['Credential'] = $Credential }
            if ($UseDCOM) { $cimParm['SessionOption'] = New-CimSessionOption -Protocol Dcom }
             
            try
            {
                $CimSession = Get-CimSession -ComputerName $aComputerName -ErrorAction Stop
            }
 
            catch
            {
                $CimSession = New-CimSession @cimParm
            }
            if (-not $CimSession)
            {
                Write-Warning "Could not connect to $ComputerName"
                continue
            }
 
            $taskList | ForEach-Object {
                   
                $x++
                            
                $compProgressParm = @{
                    CurrentOperation = $PSItem.Task
                    Activity = "$aComputerName - Triggering CCM client update Schedules"
                    Status = "$x of $($taskList.Count)"
                    PercentComplete = 100*($x/$taskList.Count)
                }
                                 
                Write-Progress @compProgressParm
 
                $taskProgressParm = @{
                    CimSession = $CimSession
                    Namespace = 'root/ccm'
                    Class = 'SMS_CLIENT'
                    Name = 'TriggerSchedule'
                    Arguments = @{ sScheduleID = $PSItem.GUID }
                    ErrorAction = 'SilentlyContinue'
                }
 
                Invoke-CimMethod @taskProgressParm
 
                if ($UseDCOM)
                {
                    $null = $CimSession | Get-CimInstance -ClassName Win32_Service -Filter "Name = 'winrm'" | Invoke-CimMethod -MethodName StartService
                }
 
            }
        }
    }
 
}
#>

Function Invoke-CCMCollectionRefresh {

    [cmdletbinding()]

    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [CimInstance[]]$Collection,
        [switch]$Wait
    )

    Begin {
        $spin = @{
            0 = '\'
            1 = '|'
            2 = '/'
            3 = '-'
        }
    }

    Process {
        foreach ($obj in $Collection) {
            $time = $obj.LastRefreshTime
            
            $null = $obj | Invoke-CimMethod -MethodName RequestRefresh

            '{0}: Collection "{1}" updated {2}' -f $MyInvocation.InvocationName, $obj.name, $obj.LastRefreshTime | Write-Verbose

            $obj = $obj | Get-CimInstance

            $x = $null

            While ( $Wait -and $obj.LastRefreshTime -eq $time -and $x -le 6000 ) {
                
                $x++
                Write-Progress -Activity 'Waiting for Collection Refresh' -Status "Collection $($obj.Name)" -CurrentOperation $spin[($x % 4)]
                if ( ($x % 30) -eq 0 ) {
                    '{0}: waiting for "{1}", {2} seconds elapsed' -f $MyInvocation.InvocationName, $obj.Name, $x | 
                        Write-Verbose
                }
                
                Start-Sleep -Seconds 1
                $obj = $obj | Get-CimInstance
            }

            '{0}: Collection "{1}" updated {2}' -f $MyInvocation.InvocationName, $obj.name, $obj.LastRefreshTime | 
                Write-Verbose

            $obj

        }
    }

}
<#This function should be moved to the CCM client module
 
Function Invoke-CCMPackageRerun
{
    [cmdletbinding()]
 
    param(
        [string[]]$ComputerName = $env:COMPUTERNAME,
        [pscredential]$Credential
    )
 
    Begin
    {
        $rerunSB = {
         
            Get-CimInstance -ClassName CCM_SoftwareDistribution -namespace root\ccm\policy\machine/ActualConfig -OutVariable Advertisements | Set-CimInstance -Property @{
                ADV_RepeatRunBehavior = 'RerunAlways'
                ADV_MandatoryAssignments = $True
            }
 
            foreach ($a_Advertisement in $Advertisements)
            {
                Write-Verbose -Message "Searching for schedule for package: $() - $($a_Advertisement.PKG_Name)"
                Get-CimInstance -ClassName CCM_Scheduler_ScheduledMessage -namespace "ROOT\ccm\policy\machine\actualconfig" -filter "ScheduledMessageID LIKE '$($a_Advertisement.ADV_AdvertisementID)%'" |
                    ForEach-Object {
 
                        $null = Invoke-CimMethod -Namespace 'root\ccm' -ClassName SMS_CLIENT -MethodName TriggerSchedule @{ sScheduleID = $PSItem.ScheduledMessageID }
 
                        [pscustomobject]@{
                            PKG_Name = $a_Advertisement.PKG_Name
                            ADV_AdvertisementID = $a_Advertisement.ADV_AdvertisementID
                            sScheduleID = $PSItem.ScheduledMessageID
                        }
                    }
            }
             
        }
 
        $ComputerList = [System.Collections.Generic.List[string]]::new()
    }
 
    Process
    {
        $ComputerList.AddRange( ([string[]]$ComputerName) )
    }
 
    End
    {
        $invokeParm = @{
                ScriptBlock = $rerunSB
        }
 
        $invokeParm['ComputerName'] = $ComputerList
         
        if ($Credential){
            $invokeParm['Credential'] = $Credential
        }
 
        if ($ComputerName -eq $env:COMPUTERNAME)
        {
            $invokeParm.Remove('Credential')
            $invokeParm.Remove('ComputerName')
        }
 
        $invokeParm | Out-String | Write-Verbose
 
        Invoke-Command @invokeParm
    }
 
}
 
#https://kelleymd.wordpress.com/2015/02/08/run-local-advertisement-with-triggerschedule/
#>

Function New-CCMCollection
{
    [cmdletbinding()]
    [Alias('New-SMS_Collection')]

    param(
        [Parameter(Mandatory)]
        [Alias('CollectionName')]
        [string]$Name,

        [ccm.CollectionType]$CollectionType,

        [Parameter(Mandatory,ParameterSetName='CollectionID')]
        [string]$LimitToCollectionID,

        [Parameter(Mandatory,ParameterSetName='Collection')]
        [ValidateScript({$PSItem.CimClass.CimClassName -eq 'SMS_Collection'})]
        [ciminstance]$LimitToCollection
    )

    Begin
    {       
        $cimHash = $sbCCMGetCimParm.InvokeReturnAsIs()
    }

    Process
    {
        $newCollectionProperty = @{
            Name = $Name
            CollectionType = [int]$CollectionType
            LimitToCollectionID = $LimitToCollectionID
        }
        if ($LimitToCollection)
        {
            $newCollectionProperty['LimitToCollectionID'] = $LimitToCollection.CollectionID
        }

        $newCollectionProperty | Out-String | Write-Verbose
        
        New-CimInstance -OutVariable newCollection @cimHash -ClassName SMS_Collection -Property $newCollectionProperty
    }
}
<#This function should be moved to the client function module
 
function Start-CCMClientComplianceSettingsEvaluation
{
    [cmdletbinding()]
     
    [alias('Start-DCMComplianceEvaluation')]
 
    param (
         
        [Parameter(ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true,
            ParameterSetName='ComputerName',
            Position=0,
            Mandatory=$true)]
        [string]$ComputerName,
 
        [Parameter(ParameterSetName='ComputerName')]
        [PSCredential]$Credential,
 
        [Parameter(ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true,
            ValueFromRemainingArguments=$false,
            ParameterSetName='CimSession',
            Mandatory=$true)]
        [Microsoft.Management.Infrastructure.CimSession]$CimSession,
 
        [switch]$WaitForEvalutaion
    )
 
    Begin
    {
        $LastComplianceStatusHash = @{
            0 = 'Non-Compliant'
            1 = 'Compliant'
            2 = 'Submitted'
            3 = 'Unknown'
            4 = 'Detecting'
            5 = 'Not Evaluated'
        }
        <#
        $StatusHash = @{
            0 = 'Idle'
            1 = 'Evaluated'
            5 = 'Not Evaluated'
        }
}
 
    Process
    {
        if (-not $CimSession)
        {
         
            try
            {
                $CimSession = Get-CimSession -ComputerName $ComputerName -ErrorAction Stop
            }
            catch
            {
                 
                $cimParm = @{
                    ComputerName = $ComputerName
                }
                if ($Credential)
                {
                    $cimParm['Credential'] = $Credential
                }
 
                $CimSession = New-CimSession @cimParm -ErrorAction Stop
            }
             
        }
 
        $systemTime = [system.management.ManagementDateTimeConverter]::ToDmtfDateTime(($CimSession | Get-CimInstance Win32_OperatingSystem).LocalDateTime.addminutes(-10))
 
        $cimParm = @{
            NameSpace = 'root\ccm\dcm'
            ClassName = 'SMS_DesiredConfiguration'
            CimSession = $CimSession
        }
 
        $baseline = Get-CimInstance @cimParm
         
        foreach ($obj in $baseline)
        {
            $null = Invoke-CimMethod -CimSession $CimSession -InputObject $obj -MethodName TriggerEvaluation -Arguments @{ Name = $obj.Name; version = $obj.Version }
        }
 
        $cimParm['Filter'] = "LastEvalTime < '$systemTime' OR LastComplianceStatus = 3"
             
        While ( $WaitForEvalutaion -and (Get-CimInstance @cimParm) -and $x -le 5)
        {
            foreach ($obj in $baseline)
            {
                if (-not $x)
                {
                    $null = Invoke-CimMethod -ErrorAction Stop -InputObject $obj -MethodName TriggerEvaluation -Arguments @{ Name = $obj.Name; version = $obj.Version }
                }
            }
             
            Write-Progress -Activity 'Refreshing compliance items' -Status "$($update.count) items remaining"
            $x++
 
            foreach ($obj in $baseline)
            {
                $null = Invoke-CimMethod -InputObject $obj -MethodName TriggerEvaluation -Arguments @{ Name = $obj.Name; version = $obj.Version }
            }
         
            Start-Sleep -Seconds 10
        }
 
        $cimParm.Remove('Filter')
 
        Get-CimInstance @cimParm | Select-Object @{Name="ComputerName";Expression={$PSItem.PSComputerName}},
            @{Name="DisplayName";Expression={ '{0}: v{1}' -f $PSItem.DisplayName,$PSItem.Version }},
            #@{Name="Status";Expression={$PSItem.Status}},
            @{Name="LastComplianceStatus";Expression={ $LastComplianceStatusHash[ [int]($PSItem.LastComplianceStatus) ] }},
            @{Name="LastEvalTime";Expression={Get-Date $PSItem.LastEvalTime}}
    }
}
#>