USC-SCCM.psm1

<#v1.5
    (Modified Send-CfgMachinePolicyUpdate to wait 2 seconds rather than 10 between downloading
    and evaluating policy)
    (Modified Send-WOL to send the WOL packet to port 1230 and port 9)
    (Modified Get-CfgClientInventory default properties)
    (Added new Install-CCM command)
    (Added Admin test to Send-Wol)
    #>

#v1.4 (Added documentation)
#v1.3 (Added Send-RepairCCM)

function Import-CfgGlobalVars {
[CmdLetBinding()]
Param()
    if ($PSVersionTable.OS -match 'Linux') {
        $VarFile = Join-Path -Path $env:HOME -ChildPath ".local/USC-SCCM/Vars.xml"
        $Unix = $True
        If (-not $Global:CfgUserName) {
            $Global:CfgUserName = Read-Host -Prompt "Enter your username if on Unix"
        }
        If (-Not $Global:CfgPassword) {
            $Global:CfgPassword = Read-Host -Prompt "Enter your password if in Unix"
        }
        If (-Not $Global:CfgDomain) {
            $Global:CfgDomain = Read-Host -Prompt "Enter your domain name if on unix"
        }
    } else {
        $VarFile = "$env:LOCALAPPDATA\USC-SCCM\Vars.xml"
    }
    #Try import SCCM SiteCode and SiteServer
    If (Test-Path $VarFile) {
        Import-Clixml -Path $VarFile | %{ Set-Variable $_.Name $_.Value -Scope Global }
    
    }
    If ($cfgSiteServer) { $connectionTestSuccess = Test-connection -ComputerName $cfgSiteServer -Count 1 -Quiet }
    while ($connectionTestSuccess -ne $True -or (-Not $cfgSiteServer)) {
        $Global:CfgSiteServer = Read-Host -Prompt "Enter your site server hostname: "
        $connectionTestSuccess = Test-connection -ComputerName $cfgSiteServer -Count 1 -Quiet 
    }

    If ($CfgSiteCode -eq $null) {
        $Global:CfgSiteCode = (Get-WmiObject -ComputerName $CfgSiteServer -Namespace root\sms -Class SMS_ProviderLocation -EA SilentlyContinue).SiteCode
        If (-Not $CfgSiteCode) {
            Write-Error "Could not obtain sitecode from $CfgSiteServer"
            return
        } Else {
            Write-Verbose "Found sitecode $CfgSitecode"
            Write-Verbose "Setting Global vars"
    
            If (-Not (Test-Path -Path (Split-Path -Path $VarFile -Parent))) {
                New-Item -Path (Split-Path -Path $VarFile -Parent) -ItemType Directory
            }

            Get-Variable Cfg* -Scope Global | Export-Clixml -Path $VarFile
        }
    }
}

function Connect-CfgSiteServer {
    <#
    .SYNOPSIS
        Setup global vars for other USC-SCCM module commandlets
    .DESCRIPTION
        Attempt to import global variables for other USC-SCCM module cmdlets, and if they don't exists, or the server cannot be contacted, prompt for input.
    .EXAMPLE
        Connect-CfgSiteServer

        Enter your site server hostname:
    .NOTES
        Author: Jesse Harris
        Date: 01/06/2018
    .LINK
        https://github.com/zigford/USC-SCCM
    #>

    [CmdLetBinding()]
    Param()

    Import-CfgGlobalVars

}

function Test-CfgGlobalVars {
    [CmdLetBinding()]
    Param()

    If (-Not $Global:CfgSiteCode -and -Not $Global:CfgSiteServer) {
        Write-Error "Please connect to site server with Connect-CfgSiteServer"
    }
}

function Test-CurrentAdminRights {
    #Return $True if process has admin rights, otherwise $False
    $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Role = [System.Security.Principal.WindowsBuiltinRole]::Administrator
    return (New-Object Security.Principal.WindowsPrincipal $User).IsInRole($Role)
 }

function Get-CfgCollectionMembers {
<#
    .SYNOPSIS
    Retreive members of an SCCM collection.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for members of a collection, based on input.
    
    .PARAMETER Collection
    The descriptive name of a collection. If spaces are in the name, surround it by quotes.
    
    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers "VMware vSphere Client 4.1 MSI WKS"
    ComputerName Collection
    ------------ ----------
    GMNQ12S VMware vSphere Client 4.1 MSI WKS
    
       
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

Param(
    [Parameter(Mandatory = $True, HelpMessage = "Please enter a collection name",
               ValueFromPipeLine = $true,
               ValueFromPipelinebyPropertyName = $True)]
               [ValidateNotNullOrEmpty()]
               [String[]]
               $Collection, $Property
)
    
Test-CfgGlobalVars -ErrorAction Stop
    
$Collections = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query `
    "Select * from SMS_Collection Where Name like '$Collection'"
ForEach ( $Col in $Collections ) {
    $Members = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query `
    "Select * from $($Col.MemberClassName)"
    #"$($Col.Name)"
      If ($Property) {
        $Members | Select-Object $Property
        }
      Else {
        $Members | Select-Object @{label='ComputerName';expression={$_.Name}},@{label='Collection';expression={$Col.Name}}
     }
}
}

function Get-CfgClientInventory {
<#
    .SYNOPSIS
    Retreive Inventory information of a Config Manager Client.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for client inventory.
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server. The percent symbol can be used inplace as a wildcard.

    .PARAMETER PrimaryUser
    If the PrimaryUser parameter is used, a search will be performed for the ConfigMgr clients where the Resource is linked to the Primary user. The percent symbol can be used inplace as a wildcard.
    
    .PARAMETER UserName
    If the UserName parameter is used, a search will be performed for ConfigMgr clients where the username matches LastLogonUserName. The percent symbol can be used inplace as a wildcard.
    
    .PARAMETER Properties
    Use the Properties parameter to specify additonal properties to load. These properties have additional network/load cost as they create additional WMI queries.
    Available properties are: VLAN, Monitor(Returns MonitorCount and MonitorRes), DockStatus, Memory, Model Manufacturer, BuildInfo, Properties.
    
   .EXAMPLE
    C:\PS>Get-CfgClientInventory 6WMPSN1
    
    ComputerName LastLogonUserName IPAddresses
    ------------ ----------------- ----------
    6WMPSN1 jpharris {169.254.71.251, 172.16.70...
   
    .EXAMPLE
    C:\PS>Get-CfgClientInventory -UserName jpha%

    ComputerName LastLogonUserName IPAddresses
    ------------ ----------------- ----------
    6WMPSN1 jpharris {169.254.71.251, 172.16.70...
    VDI-WIN7X86-016 jpharris {10.0.2.19, fe80::ed59:db1...
    
    .EXAMPLE
    C:\PS>Get-CfgClientInventory 6WMPSN1 -Properties VLAN,Model,Monitor,DockStatus
        Returns additional properties of system 6wmpsn1
        
    .EXAMPLE
    C:\PS>Get-CfgClientInventory 6WMPSN1 -Property Name,IPAddresses,MACAddresses
    
    Name IPAddresses Macaddresses
    ---- ----------- ------------
    6WMPSN1 203.57.189.58 00:50:56:C0:00:01
    
    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers -Collection "Lab DG40" | Get-CfgClientInventory -Properties BuildInfo
        Returns Build times and management point for the collection members of Lab DG40
        
    .EXAMPLE
    C:\PS>Get-CfgClientInventory 6WMPSN1 | Select -ExpandProperty IPAddresses
    169.254.71.251
    172.16.70.159
    192.168.201.1
    203.57.189.153
    fe80::954a:bf66:6607:4206
    fe80::b1af:461b:efa:176
    fe80::b83a:7f75:cfcc:47fb
    fe80::fda2:bc20:ea38:6c80

    .EXAMPLE
    C:\PS>Get-CfgClientInventory -PrimaryUser '%jpharris' -Properties PrimaryUser

    ComputerName LastLogonUserName IPAddresses PrimaryUser
    ------------ ----------------- ----------- -----------
    049660345053 slawford {10.205.80.33} {USC\AdminJPHarris, usc\lgoldsbo, USC\slaw
    WSP-LICENSE01 {10.104.0.191} {usc\adminjpharris, usc\adminlgoldsbo


    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
    1.1 - 10/04/2012 - Modified default properties and docs
    1.2 - 19/04/2018 - Added PrimaryUser property and search on primary user

    .LINKS
    https://github.com/zigford/USC-SCCM
#>

  [CmdletBinding(DefaultParameterSetName="Computer")]

    Param(
        [Parameter(
            ValueFromPipeline=$True,
            ValueFromPipelinebyPropertyName=$True,
            ParameterSetName="Computer")]
        [Parameter(
            Position = 0    
        )]
        [string[]]$ComputerName="$env:computername",
        [Parameter(
            ParameterSetName="User"
        )]
        [string]$UserName,
        [Parameter(
            ParameterSetName="PrimaryUser"
        )]
        $PrimaryUser,
        $Properties,
        [Switch]$ExtendedData
    )

BEGIN {
    Test-CfgGlobalVars -ErrorAction Stop
}

PROCESS {

      function CfgClientInventory-Worker {
        Param($Name,$User,$PrimaryUser)
        #Set Default Object properties
        $defaultProperties = @('ComputerName','LastLogonUserName', 'IPAddresses')

        If ($Name -ne $null) {
            $Query = "Select * from SMS_R_System Where Name like '$Name'"
            $QueryResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $Query
        } ElseIf ($User) {
            $Query = "Select * from SMS_R_System Where LastLogonUserName like '$User'"
            $QueryResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $Query
        } elseif ($PrimaryUser) {
            $UDAQuery = "Select * from SMS_UserMachineRelationship Where UniqueUserName like '$PrimaryUser'"
            $UDAResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $UDAQuery
            $QueryResults = $UDAResults | ForEach-Object {
                Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query "Select * from SMS_R_System Where ResourceID = '$($_.ResourceID)'"
            }
        }
        Foreach ($Result in $QueryResults) {
            $MonitorRes = $null
            $MonitorCount = $null
            $Result | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Result.Name
            Switch ($Properties) {
                <#VLAN {
                    $VLANQuery = 'Select Ranges from SMS_G_System_USC_MOEVLAN Where ResourceID = "' + $Result.ResourceID + '"'
                    $Result | Add-Member -MemberType NoteProperty -Name VLAN -Value (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $VLANQuery).Ranges
                    $defaultProperties += "VLAN"
                    }#>

                Model {
                    $ModelQuery = 'Select Model from SMS_G_System_COMPUTER_SYSTEM Where ResourceID = "' + $Result.ResourceID + '"'
                    $Result | Add-Member -MemberType NoteProperty -Name Model -Value (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $ModelQuery).Model
                    $defaultProperties += "Model"
                    }
                PrimaryUser {
                    $PrimaryUserQuery = 'Select * from SMS_UserMachineRelationship Where ResourceID = "' + $Result.ResourceID + '"'
                    $UDAData = Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $PrimaryUserQuery
                    If ($UDAData) {
                        $Result | Add-Member -MemberType NoteProperty -Name PrimaryUser -Value $UDAData.UniqueUserName
                        $defaultProperties += "PrimaryUser"
                    }
                }
                Monitor {
                    $MonitorQuery = 'Select * from SMS_G_System_DESKTOP_MONITOR Where ResourceID = "' + $Result.ResourceID + '"'
                    $MonitorData = (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $MonitorQuery)
                    $MonitorData | Where-Object {$_.ScreenWidth -ne $null} | ForEach-Object {$MonitorRes += "$($_.ScreenWidth)x$($_.ScreenHeight),"
                    $MonitorCount ++}
                    $MonitorRes = $MonitorRes -replace ",$",""
                    $Result | Add-Member -MemberType NoteProperty -Name MonitorCount -Value $MonitorCount
                    $Result | Add-Member -MemberType NoteProperty -Name MonitorRes -Value $MonitorRes
                    $defaultProperties += "MonitorCount","MonitorRes"
                    }
                Memory {
                    $MemoryQuery = 'Select TotalPhysicalMemory from SMS_G_System_X86_PC_MEMORY Where ResourceID = "' + $Result.ResourceID + '"'
                    $Number = (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $MemoryQuery).TotalPhysicalMemory /1kb
                    $Memory = "{0:N0}" -f $Number + " MB"
                    $Result | Add-Member -MemberType NoteProperty -Name Memory -Value $Memory
                    $defaultProperties += "Memory"
                    }       
                Warranty {
                    $WarrantyQuery = 'Select WarrantyEndDate from SMS_G_System_USCWarrantyInfo Where ResourceID = "' + $Result.ResourceID + '"'
                    $Result | Add-Member -MemberType NoteProperty -Name WarrantyEndDate -Value (Get-WMIObject -ComputerName $CfgSiteServer -NameSpace "root\sms\site_$($CfgSiteCode)" -Query $WarrantyQuery).WarrantyEndDate
                    $defaultProperties += "WarrantyEndDate"
                    }
                <#DockStatus {
                    $DockQuery = 'Select DockingState from SMS_G_System_USC_DOCKINFO Where ResourceID = "' + $Result.ResourceID + '"'
                    $DockStatus = Switch ((Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $DockQuery).DockingState) {
                        0 {"Unsupported"}
                        1 {"UnDocked"}
                        2 {"Docked"}
                        3 {"UnKnown"}
                    }
                    $Result | Add-Member -MemberType NoteProperty -Name DockStatus -Value $DockStatus
                    $defaultProperties += "DockStatus"
                    }#>

                VideoCard {
                    $VCardQuery = 'Select Description,DriverVersion from SMS_G_System_VIDEO_CONTROLLER Where ResourceID = "' + $Result.ResourceID + '"'
                    $VCard = (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $VCardQuery)
                    $Result | Add-Member -MemberType NoteProperty -Name VideoCard -Value $VCard.Description
                    $Result | Add-Member -MemberType NoteProperty -Name DriverVersion -Value $VCard.DriverVersion
                    $defaultProperties += "VideoCard","DriverVersion"
                    }
                Manufacturer {
                    $ManufacturerQuery = 'Select Manufacturer from SMS_G_System_COMPUTER_SYSTEM Where ResourceID = "' + $Result.ResourceID + '"'
                    $Result | Add-Member -MemberType NoteProperty -Name Manufacturer -Value (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $ManufacturerQuery).Manufacturer
                    $defaultProperties += "Manufacturer"
                    }
                BuildInfo {
                    $BuildInfoQuery = 'Select * from SMS_G_System_MOETATTOO Where ResourceID = "' + $Result.ResourceID + '"'
                    $BuildInfo = (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $BuildInfoQuery)
                    ForEach ($Build in $BuildInfo) {
                        If ($Build.BuildTimestamp -ne $null) {
                            $Result | Add-Member -MemberType NoteProperty -Name BuildTimestamp -Value ([datetime]::ParseExact([string]$Build.BuildTimestamp,"yyyyMMddHHmmss.000000+600",$null))
                            $Result | Add-Member -MemberType NoteProperty -Name BuildManagementPoint -Value $Build.BuildManagementPoint
                            $Result | Add-Member -MemberType NoteProperty -Name BuildTSName -Value $Build.BuildTSName
                            $Result | Add-Member -MemberType NoteProperty -Name CoreImageTimestamp -Value ([datetime]::ParseExact([string]$Build.CoreImageTimestamp,"yyyyMMddHHmmss.000000+600",$null))
                            $Result | Add-Member -MemberType NoteProperty -Name CoreImageTSName -Value $Build.CoreImageTSName
                            $defaultProperties = @('ComputerName','BuildTimestamp','BuildTSName')
                        }
                    }
                }
                
            }
            $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
            $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
            $Result | Add-Member MemberSet PSStandardMembers $PSStandardMembers -PassThru
        }
        
      }

    If ($UserName) { 
        CfgClientInventory-Worker -User $UserName
    } Elseif ($PrimaryUser) {
        CfgClientInventory-Worker -PrimaryUser $PrimaryUser
    } Else {
        If ($PSBoundParameters.ContainsKey('ComputerName')) {
            Foreach ($Computer in $ComputerName) {
                CfgClientInventory-Worker -Name $Computer
            } 
        } Else {
            CfgClientInventory-Worker -Name $ComputerName
        }
    }
  }
}

function Test-CfgCollectionMembers {
<#
    .SYNOPSIS
    Performs Test-Connection on the members of the specified collection.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for members of a collection, then reports on their network connectivity.
    
    .PARAMETER Collection
    The descriptive name of a collection. If spaces are in the name, surround it by quotes.
    
   .EXAMPLE
    C:\PS>Test-CfgCollectionMembers
    
    87VZ72S is unavailable
    F7VZ72S is up
    67VZ72S is unavailable
    57VZ72S is up
    B7VZ72S is unavailable
    G7VZ72S is unavailable
    47VZ72S is unavailable
    28VZ72S is unavailable
    J7VZ72S is unavailable
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

Param($Collection)

Test-CfgGlobalVars -ErrorAction Stop

Get-CfgCollectionMembers -Collection $Collection | `
  ForEach-Object { 
        $TestConn = Test-Connection -ComputerName $_.ComputerName -Count 1 -ErrorAction SilentlyContinue
        If ( $TestConn ) { 
            $_ | Add-Member -MemberType NoteProperty -Name "Online" -Value $True
            Try {
                $UserName = (Get-WmiObject -ComputerName $_.ComputerName -Class Win32_computersystem).UserName
            } Catch {
                $UserName = $False
            }
            If ($UserName.Count -gt 0 -and ($UserName.StartsWith('USC\'))) {$UserName = $UserName.TrimStart('USC\')}
            $_ | Add-Member -MemberType NoteProperty -Name "CurrentUser" -Value $UserName
            
        } Else {
            $_ | Add-Member -MemberType NoteProperty -Name "Online" -Value $False
            $_ | Add-Member -MemberType NoteProperty -Name "CurrentUser" -Value $False
        }
        $_
  }
}

function Send-CfgUserUpdateTrigger {
    Param($ComputerName)
    
    $sid = ( get-wmiobject -ComputerName $ComputerName -query "SELECT UserSID FROM CCM_UserLogonEvents WHERE LogoffTime = NULL" -namespace "ROOT\ccm").UserSID.replace('-','_');
    $sched=([wmi]"\\$ComputerName\root\ccm\Policy\$sid\ActualConfig:CCM_Scheduler_ScheduledMessage.ScheduledMessageID='{00000000-0000-0000-0000-000000000026}'");
    $sched.Triggers=@('SimpleInterval;Minutes=1;MaxRandomDelayMinutes=0');
    $sched.Put()
}


function Get-CfgCacheSize {
Param($ComputerName=$env:ComputerName)
    $Cache = ([WMI]"\\$ComputerName\Root\ccm\SoftmgmtAgent:CacheConfig.ConfigKey='Cache'").Size
    $Cache
}

function Get-CMCacheInfo {
    $OUIResource = New-Object -ComObject UIResource.UIResourceMgr
    $OUIResource.GetCacheInfo()
}

function Clear-CMCache {
    <#
    .SYNOPSIS
    Connects to the Config Manager client via Com object and requests cache deletion on items with a reference count of 0.
    
    .DESCRIPTION
    Connects to a PC using WinRM and invokes a script to cleanup cache items which are no longer in use by the client.
    
    .PARAMETER ComputerName
    The hostname of a computer to connect to. If omitted, works on the local host if there are sufficient rights.
    
   .EXAMPLE
    C:\PS>Get-CfgCollectionMembers | Clear-CMCache -Verbose
    
    VERBOSE: Attempting connection to SME-TEST02
    VERBOSE: Invoking Scriptblock
    VERBOSE: Getting cache object
    VERBOSE: Cache Location C:\WINDOWS\ccmcache\2s has 0 references. Deleting..
    VERBOSE: Cache Location C:\WINDOWS\ccmcache\2u has 0 references. Deleting..
    VERBOSE: Cache Location C:\WINDOWS\ccmcache\2v has 0 references. Deleting..
    VERBOSE: Cache Location C:\WINDOWS\ccmcache\2x cannot be removed as it has 1 reference.

    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 05 Feb 2018
    ChangeLog:
    1.0 - First Release
#>

    [CmdletBinding()]
    Param(
        [Parameter(
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True
        )]$ComputerName)
    Begin {
        $ScriptBlock = {
            [CmdletBinding()]
            Param($VerbosePreference)

            #$VerbosePreference = 'Continue'
            function Get-CMCacheInfo {
                [CmdLetBinding()]
                Param()

                $OUIResource = New-Object -ComObject UIResource.UIResourceMgr
                $OUIResource.GetCacheInfo()
            }
            Write-Verbose "Getting cache object"
            $Cache = Get-CMCacheInfo
            ForEach ($CacheObj in $Cache.GetCacheElements()) {
                Switch ($CacheObj.ReferenceCount) {
                    {$_ -gt 1} { Write-Verbose "Cache Location $($CacheObj.Location) cannot be removed as it has $($CacheObj.ReferenceCount) references." }
                    1 { Write-Verbose "Cache Location $($CacheObj.Location) cannot be removed as it has 1 reference." }
                    Default {
                        Write-Verbose "Cache Location $($CacheObj.Location) has 0 references. Deleting.."
                        Try {
                            $Cache.DeleteCacheElement($CacheObj.CacheElementId)
                        } Catch {
                            Write-Warning "Failed to remove cache location"
                        }
                    }
                }
            }
        }
    }

    Process {
        If ($ComputerName.ComputerName) {
            $ComputerName = $ComputerName.ComputerName
        }
        If ($ComputerName) {
            Write-Verbose "Attempting connection to $ComputerName"
            If (-Not (Test-Connection -ComputerName $ComputerName -Count 1 -TimeToLive 7 -Quiet)) {
                Write-Error "Device $ComputerName not online"
            } else {
                $WinRM = Get-Service -ComputerName $ComputerName -Name WinRM
                If ($WinRM.Status -eq 'Stopped') { $WinRM.Start() }
                If (-Not (Test-WSMan -ComputerName $ComputerName)) {
                    Write-Error 'Unable to connect to WinRM service'
                } else {
                    Write-Verbose "Invoking Scriptblock"
                    Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock -ArgumentList $VerbosePreference
                }
            }
        } else {
            Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $VerbosePreference
        }
    }
}

function Set-CfgCacheSize {
[CmdletBinding()]
Param($ComputerName=$env:ComputerName,$Size=25000,[Switch]$Percentage)
    If ($Percentage) {
        #Check if size is written in percent
        If ($Size -gt 99) {
            Write-Error "Size is not a percentage. Please specify size as a percentage when using the percentage parameter"
            return
        }
        #Okay, lets work out what the size will be if it is a percentage of the current disk.
        # 1. Get disk info
        $DiskInfo = Get-WmiObject -ComputerName $ComputerName -Class Win32_LogicalDisk | Where-Object {$_.DeviceID -eq 'C:'}
        # 2. Get the freespace of C:
        $Freespace =$DiskInfo.FreeSpace 
        # 3. Get the full size of the disk
        $DiskSize = $DiskInfo.Size
        # 4. Work out the percentage of total size
        $PercentOfTotal = $DiskSize/100*$Size
        # 5. Do we have this amount available in freespace whilst retaining 10 gig for the OS
        If (($Freespace - $PercentOfTotal) -gt 10240) {
            #Yes we will be able to do this
            $FreeSpaceHR = "{0:#00}Gb" -f ($Freespace /1gb)
            $PercentOfTotalHR = "{0:#00}Gb" -f ($PercentOfTotal /1gb)
            Write-Verbose "Freespace is $FreeSpaceHR" 
            Write-Verbose "We will consume $PercentOfTotalHR"
            #Convert Bytes to Megabytes
            $SizeinMB = $PercentOfTotal/1024/1024
            $Size = [int]$SizeinMB
        } Else {
            #No, not enough space available
            Write-Error "Not enough space available to consume $Size % of disk"
            return
        }
    }
    
    $a=([wmi]"\\$ComputerName\ROOT\ccm\SoftMgmtAgent:CacheConfig.ConfigKey='Cache'")
    $a.Size=$Size
    $a.Put()
    If ($? -eq $true) {
        Write-Verbose "Succesfully set cache size to $Size Mb"
    }
}

function Send-CfgAppEval {
<#
    .SYNOPSIS
    Performs a Application Deployment evaluation cycle on the specified ConfigMgr client.
    
    .DESCRIPTION
    Connect to the WMI namespace of the specified machine and executes a method to trigger the schedule
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.
    
   .EXAMPLE
    C:\PS>Send-CfgAppEval -ComputerName 9k9562s
    Executing AppEval for 9k9562s
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Send-CfgAppEval
        Attempts to send an application deployment cycle to members of the collection "Lab DG40"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName)
PROCESS {

    function SendMachineUpdate-Worker {
        Param($sName)
        $SCCMClient = [WMIClass]"\\$sName\Root\CCM:SMS_Client"
        Write-Host "Executing AppEval for $sName"
        Try {$SCCMClient.psbase.InvokeMethod("TriggerSchedule", "{00000000-0000-0000-0000-000000000121}") }
        Catch { "An Error occured" }
    }
    
    If ($PSBoundParameters.ContainsKey('ComputerName')) {
        ForEach ($Computer in $ComputerName) {
            SendMachineUpdate-Worker -sName $Computer
            }
        } Else {
            SendMachineUpdate-Worker -sName $ComputerName
        }
    }
}

function Send-CfgTrigger {
<#
    .SYNOPSIS
    Performs a machine policy update on the specified ConfigMgr client.
    
    .DESCRIPTION
    Connect to the WMI namespace of the specified machine and executes a method to download and evaluate machine policy
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.
    
   .EXAMPLE
    C:\PS>Send-CfgSCEPTrigger -ComputerName 9k9562s
    Downloading Policy for 9k9562s
    Evaluating Policy for 9k9562s
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Send-CfgSCEPTrigger
        Attempts to send a machine policy update evaluation to members of the collection "Lab DG40"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName,
      [ValidateSet(
          'Machine',
          'User',
          'HWInventory',
          'SCEP',
          'UpdateScan'
          )][string[]]$TriggerName)
      
BEGIN {
    $Delay = 1
    $Triggers = Switch ($TriggerName) {
        Machine {'{00000000-0000-0000-0000-000000000021}','{00000000-0000-0000-0000-000000000022}'}
        User {'{00000000-0000-0000-0000-000000000026}','{00000000-0000-0000-0000-000000000027}' }
        HWInventory {'{00000000-0000-0000-0000-000000000001}'}
        SWInventory {'{00000000-0000-0000-0000-000000000002}'}
        SCEP {'{00000000-0000-0000-0000-000000000221}'}
        UpdateScan {
            '{00000000-0000-0000-0000-000000000113}','{00000000-0000-0000-0000-000000000108}'
            $Delay = 15    }
        Default {'{00000000-0000-0000-0000-000000000021}','{00000000-0000-0000-0000-000000000022}'}
    }
}
PROCESS {

    function SendMachineUpdate-Worker {
        Param($sName,$Trigger,$Delay)
        $SCCMClient = [WMIClass]"\\$sName\Root\CCM:SMS_Client"
        Write-Verbose "Executing trigger $Trigger on $sName"
        Try {$SCCMClient.psbase.InvokeMethod("TriggerSchedule", $Trigger) }
        Catch { "An Error occured" }
        Write-Verbose "Sleeping for $Delay Seconds"
        Start-Sleep -Seconds $Delay
    }
    
    If ($PSBoundParameters.ContainsKey('ComputerName')) {
        ForEach ($Computer in $ComputerName) {
            $Triggers | ForEach-Object { SendMachineUpdate-Worker -sName $Computer -Trigger $_ -Delay $Delay }
            }
        } Else {
            $Triggers | ForEach-Object { SendMachineUpdate-Worker -sName $ComputerName -Trigger $_ -Delay $Delay}
        }
    }
}

function Send-CfgSCEPTrigger {
<#
    .SYNOPSIS
    Performs a machine policy update on the specified ConfigMgr client.
    
    .DESCRIPTION
    Connect to the WMI namespace of the specified machine and executes a method to download and evaluate machine policy
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.
    
   .EXAMPLE
    C:\PS>Send-CfgSCEPTrigger -ComputerName 9k9562s
    Downloading Policy for 9k9562s
    Evaluating Policy for 9k9562s
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Send-Send-CfgSCEPTrigger
        Attempts to send a machine policy update evaluation to members of the collection "Lab DG40"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName)
PROCESS {

    function SendMachineUpdate-Worker {
        Param($sName)
        $SCCMClient = [WMIClass]"\\$sName\Root\CCM:SMS_Client"
        Write-Host "Downloading Policy for $sName"
        Try {$SCCMClient.psbase.InvokeMethod("TriggerSchedule", "{00000000-0000-0000-0000-000000000221}") }
        Catch { "An Error occured" }
    }
    
    If ($PSBoundParameters.ContainsKey('ComputerName')) {
        ForEach ($Computer in $ComputerName) {
            SendMachineUpdate-Worker -sName $Computer
            }
        } Else {
            SendMachineUpdate-Worker -sName $ComputerName
        }
    }
}

function Send-CfgMachineUpdateTrigger {
<#
    .SYNOPSIS
    Performs a machine policy update on the specified ConfigMgr client.
    
    .DESCRIPTION
    Connect to the WMI namespace of the specified machine and executes a method to download and evaluate machine policy
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.

    .PARAMETER Force
    Send a hard policy reset prior to a regular machine policy evlauation cyle
    
   .EXAMPLE
    C:\PS>Send-CfgMachineUpdateTrigger -ComputerName 9k9562s
    Downloading Policy for 9k9562s
    Evaluating Policy for 9k9562s
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Send-CfgMachineUpdateTrigger
        Attempts to send a machine policy update evaluation to members of the collection "Lab DG40"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName,
      [switch]$Force,
      [switch]$Confirm=$True)
PROCESS {

    function SendMachineUpdate-Worker {
        Param($sName)
        $SCCMClient = [WMIClass]"\\$sName\Root\CCM:SMS_Client"
        If ($Force) {
            If ($Confirm) {
                While ($ans -notin 'y','n') {
                    $ans = Read-Host -Prompt "Using the force parameter will cause a full machine policy reset. Are you sure you wish to continue ? (y/n)"
                }
                If ($ans -eq 'y') {
                    $AllowReset = $True
                }
            } else {
                $AllowReset = $True
            }
            If ($AllowReset) {
                Write-Verbose "Forcing policy reset"
                $SCCMClient.ResetPolicy(1)
                $SCCMClient.psbase.InvokeMethod("TriggerSchedule", "{00000000-0000-0000-0000-000000000040}")
            }
        }
        Write-Verbose "Downloading Policy for $sName"
        Try {$SCCMClient.psbase.InvokeMethod("TriggerSchedule", "{00000000-0000-0000-0000-000000000021}") }
        Catch { "An Error occured" }
        Start-Sleep -Seconds 2
        Write-Verbose "Evaluating Policy for $sName"
        Try {$SCCMClient.psbase.InvokeMethod("TriggerSchedule", "{00000000-0000-0000-0000-000000000022}") }
        Catch { "An Error occured" }
    }
    
    If ($PSBoundParameters.ContainsKey('ComputerName')) {
        ForEach ($Computer in $ComputerName) {
            SendMachineUpdate-Worker -sName $Computer
            }
        } Else {
            SendMachineUpdate-Worker -sName $ComputerName
        }
    }
}

function Send-CfgInventoryUpdateTrigger {
<#
    .SYNOPSIS
    Performs a hardware inventory on the specified ConfigMgr client.
    
    .DESCRIPTION
    Connect to the WMI namespace of the specified machine and executes a method to execute hardware inventory
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.

    .PARAMETER Full
    Forces a full inventory report rather than a delta. This is achived by deleting the previous inventory which causes a version mismatch.
    
   .EXAMPLE
    C:\PS>Send-CfgInventoryUpdateTrigger -ComputerName 9k9562s
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Send-CfgInventoryUpdateTrigger
        Attempts to send a WMI method to execute hardware inventory update to members of the collection "Lab DG40"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

Param($ComputerName,[switch]$Full)
If ($Full) {
    $invVersion = Get-WmiObject -ComputerName $ComputerName -Namespace Root\ccm\invagt -Class InventoryActionStatus | Where-Object {$_.InventoryActionID -eq "{00000000-0000-0000-0000-000000000001}"}
    $invVersion.Delete()
}
If ( ($ComputerName) ) {
$SCCMClient = [WMIClass]"\\$ComputerName\Root\CCM:SMS_Client"
}
Else { 
$SCCMClient = [WMIClass]"Root\CCM:SMS_Client"
}
$SCCMClient.TriggerSchedule("{00000000-0000-0000-0000-000000000001}")
}

function Get-CfgClientProvisioningMode {
  (Get-ItemProperty "HKLM:\Software\Microsoft\CCM\CCMExec" -Name "ProvisioningMode").ProvisioningMode
}

function Set-CfgClientProvisioningMode {
   Set-ItemProperty "HKLM:\Software\Microsoft\CCM\CCMExec" -Name "ProvisioningMode" -Value "False" -Force
   Set-ItemProperty "HKLM:\Software\Microsoft\CCM\CCMExec" -Name "SystemTaskExcludes" -Value "" -Force
   Remove-ItemProperty -Path "HKLM:\Software\Microsoft\SMS\Task Sequence" -Name Package -ea SilentlyContinue
   Remove-ItemProperty -Path "HKLM:\Software\Microsoft\SMS\Task Sequence" -Name "Active Request Handle" -ea SilentlyContinue
   Remove-ItemProperty -Path "HKLM:\Software\Microsoft\SMS\Task Sequence" -Name CleanUpFolder -ea SilentlyContinue
   Remove-ItemProperty -Path "HKLM:\Software\Microsoft\SMS\Task Sequence" -Name Program -ErrorAction SilentlyContinue
}

function Get-CfgCollections {
    <#
            .SYNOPSIS
                Determine the SCCM collection membership
            .DESCRIPTION
                This function allows you to determine the SCCM collection membership of a given user/computer
            .PARAMETER Type
                Specify the type of member you are querying. Possible values : 'User' or 'Computer'
            .PARAMETER ResourceName
                Specify the name of your member : username or computername
            .EXAMPLE
                Get-CfgCollections -Type computer -ResourceName PC001
                Get-CfgCollections -Type user -ResourceName User01
            .Notes
                Author : Antoine DELRUE
                Edited : Jesse Harris
                WebSite: http://obilan.be
    #>
 
    [CmdLetBinding()]
      param(
    [Parameter(Mandatory=$false,Position=2)]
    [ValidateSet("User", "Computer")]
    [string]$type="Computer",

    [Parameter(Mandatory=$true,Position=1)]
    [string]$ResourceName
    ) #end param

    Test-CfgGlobalVars -ErrorAction Stop

    Switch ($type)
        {
            User {
                Try {
                    $ErrorActionPreference = 'Stop'
                    $resource = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$CfgSiteCode" -Class "SMS_R_User" | ? {$_.Name -ilike "*$resourceName*"}                            
                }
                catch {
                    Write-Warning ('Failed to access "{0}" : {1}' -f $CfgSiteServer, $_.Exception.Message)
                }

            }

            Computer {
                Try {
                    $ErrorActionPreference = 'Stop'
                    $resource = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$CfgSiteCode" -Class "SMS_R_System" | ? {$_.Name -ilike "$resourceName"}                           
                }
                catch {
                    Write-Warning ('Failed to access "{0}" : {1}' -f $CfgSiteServer, $_.Exception.Message)
                }
            }
        }

    $ids = (Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$CfgSiteCode" -Class SMS_CollectionMember_a -filter "ResourceID=`"$($Resource.ResourceId)`"").collectionID
    # A little trick to make the function work with SCCM 2012
    if ($ids -eq $null)
    {
            $ids = (Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$CfgSiteCode" -Class SMS_FullCollectionMembership -filter "ResourceID=`"$($Resource.ResourceId)`"").collectionID
    }

    $array = @()

    foreach ($id in $ids)
    {
        $Collection = get-WMIObject -ComputerName $CfgSiteServer -namespace "root\sms\site_$CfgSiteCode" -class sms_collection -Filter "collectionid=`"$($id)`""
        $Object = New-Object PSObject
        $Object | Add-Member -MemberType NoteProperty -Name "CollectionName" -Value $Collection.Name
        $Object | Add-Member -MemberType NoteProperty -Name "CollectionID" -Value $id
        $Object | Add-Member -MemberType NoteProperty -Name "Comment" -Value $Collection.Comment
        $array += $Object
    }

    $array
}

function Get-CfgMachineVariables {
    <#
        .SYNOPSIS
            Determin all variables a machine has assigned
        .DESCRIPTION
            This function allows you to see machine and collection based variables a machine will eventually have
        .PARAMETER ComputerName
            Specify the name of the computer you want to query of variables
        .PARAMETER IncludeCollections
            Switch to also query collections. Disabled by default as it incurrs siginificant processing cost
        .EXAMPLE
            Get-CfgMachineVariables -ComputerName SME-Test03 -IncludeCollections
        .NOTES
            Author : Jesse Harris
            Website: github.com\zigford
            Version 1.1 - 25/01/2018 - Added collection precedence property
    #>

  [CmdletBinding()]

      Param(
      [Parameter(ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName="$env:computername",
      [Switch]$IncludeCollections,
      $Property)

BEGIN {
    Test-CfgGlobalVars -ErrorAction Stop
}
PROCESS {

      function CfgClientInventory-Worker {
        Param($Name)
        $ResourceID = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" `
            -Query "Select ResourceID from SMS_R_System Where Name = '$Name'" | Select-Object -ExpandProperty ResourceID
        Write-Verbose "ResourceID = $ResourceID"

        function Get-MachineLevelVars {
            Param($ResourceID)
            Write-Verbose "Getting machine level vars"
            $QueryResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" `
                -Query "Select * from SMS_MachineSettings Where ResourceID = '$ResourceID'"
            $QueryResults.Get()
            ForEach ($Var in $QueryResults.MachineVariables) {
                [PSCustomObject]@{
                    'ComputerName' = $Name
                    'Name' = $Var.Name
                    'Value' = $Var.Value
                    'Source' = $Name
                    'Precedence' = 0
                }
            }
         
        }

        function Get-CollectionLevelVars {
            Param($Name)
            Write-Verbose "Getting collection level vars"
            Get-CfgCollections -type Computer -ResourceName $Name | ForEach-Object {
                $QueryResult = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query `
                    "Select * From SMS_CollectionSettings Where CollectionID = '$($_.CollectionID)'"
                Write-Verbose "Running Get Method on $($_.CollectionName) with CollectionID $($_.CollectionID)"
                If ($QueryResult) {
                    $QueryResult.Get()
                    ForEach ($Var in $QueryResult.CollectionVariables) {
                        [PSCustomObject]@{
                            'ComputerName' = $Name
                            'Name' = $Var.Name
                            'Value' = $Var.Value
                            'Source' = $_.CollectionName
                            'Precedence' = $QueryResult.CollectionVariablePrecedence
                        }
                    }
                }
            }           
        }

        Get-MachineLevelVars -ResourceID $ResourceID
        If ($IncludeCollections) {
            Get-CollectionLevelVars -Name $Name
        }
}

If ($PSBoundParameters.ContainsKey('ComputerName')) {
      Foreach ($Computer in $ComputerName) {
        CfgClientInventory-Worker -Name $Computer
      } 
    } Else {
        CfgClientInventory-Worker -Name $ComputerName
    }
  }
}

function Send-WOL {
    <#
        .SYNOPSIS
        Sends a WOL magic packet to wake a ConfigMgr client.
        
        .DESCRIPTION
        Connect to the WMI namespace of the site server, retreives the MAC addresses of a specified ConfigMgr client and generates WOL packets for each of those MAC addresses.
        
        .PARAMETER ComputerName
        The name of a ConfigMgr client, registered with the Site Server.
        
       .EXAMPLE
        C:\PS>Send-WOL -ComputerName 6wmpsn1
        Wake-On-Lan magic packet of length 102 sent to 00:50:56:C0:00:01
    
        Wake-On-Lan magic packet of length 102 sent to 00:50:56:C0:00:08
    
        Wake-On-Lan magic packet of length 102 sent to 1C:65:9D:98:8E:84
    
        Wake-On-Lan magic packet of length 102 sent to F0:4D:A2:59:80:4F
        
        .EXAMPLE
        Get-CfgCollectionMembers -Collection "Lab DG40" | Send-WOL
            Sends a WOL magic packet for MAC addresses of members of collection Lab DG40
            
        .NOTES
        Author: Jesse Harris
        For: University of Sunshine Coast
        Date Created: 09 Jan 2012
        ChangeLog:
        1.0 - First Release
        1.1 - 10/04/2012 - Tests admin rights to bind on privlidged ports and sends wol on port 1230 and port 9
    #>

    param(
        [CmdletBinding()]
        [Parameter(ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True)]
        [string[]]$ComputerName = "$env:computername",
        $SendFrom,
        [switch]$BroadCast,
        $Ports = 9,
        $MacAddress)

    BEGIN {
        Test-CfgGlobalVars -ErrorAction Stop
        $Proxies = @{}
    }
    PROCESS {
        function Send-WolWorker {
            Param($Name, $Port, $MacAddress, $From, [Switch]$BroadCast)
            $BroadcastString = if ($Broadcast) { "Broadcast"} else {"Unicast"}
            $FromString = if ($From) { "From $From" } else { "From this machine" }
            write-verbose "Initiating WOL - $FromString To: $Name Port: $Port $Broadcaststring"
            $Query = "Select MACAddresses,IPAddresses from SMS_R_System Where SMS_R_System.Name = '$Name' AND SMS_R_System.Active = '1'"
            $MachineResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace Root\SMS\Site_$CfgSiteCode -Query $Query
            If ( $MachineResults -eq $null ) {
                write-warning "No SCCM info returned for machine $Name"
                return 0
            }
            Foreach ($MachineResult in $MachineResults) {
                If ( $MachineResult.MacAddresses -eq $null ) {
                    write-warning "No Mac addresses found for $Name"
                    return 0
                }
                Foreach ($MacAddress in $MachineResult.MacAddresses) {
                    If ($BroadCast) {
                        $IPDests = '255.255.255.255'
                    }
                    Else {
                        $IPDests = $MachineResults.IPAddresses
                    }

                    ForEach ($IPDest in $IPDests) {
                        $ParsedIP = [System.Net.IPAddress]::Parse($IPDest)
                        If ($ParsedIP.AddressFamily -eq 'InterNetwork') {
                            Write-Verbose "$($ParsedIP.IPAddressToString) detected as IPv4"
                            $mac = $MacAddress.split(':') | % { [byte]('0x' + $_) }
                            $ScriptBlock = {
                                [CmdLetBinding()]
                                Param($Port, $mac, $ParsedIP, $VerbosePreference)
                                Write-Verbose "Parsed IP: $($ParsedIP.IPAddressToString)"
                                Write-Verbose "Port: $Port"
                                $packet = [byte[]](, 0xFF * 6)
                                $packet += $mac * 16
                                Write-Verbose "Packet Len: $($packet.Length)"
                                $UDPclient = new-Object System.Net.Sockets.UdpClient
                                $UDPclient.Connect($ParsedIP, $Port)
                                [void] $UDPclient.Send($packet, $packet.Length)
                            }
                            If ($From -ne $null) {
                                Invoke-command -AsJob -ComputerName $From -ScriptBlock $ScriptBlock -ArgumentList $Port, $mac, $ParsedIP, $VerbosePreference > $Null
                                Write-Verbose "Wake-On-Lan magic packet sent to port $Port on $Name $MacAddress from $From as broadcast`n"
                            }
                            else {
                                Invoke-Command $ScriptBlock -ArgumentList $Port, $mac, $ParsedIP, $VerbosePreference
                                Write-Verbose "Wake-On-Lan magic packet sent to port $Port on $Name $MacAddress from localhost as unicast`n"
                            }
                        }
                    }
                }
            }
        }
        function Test-SendFrom($SendFrom) {
            If (-not ($SendFrom -and (Test-Connection -ComputerName $SendFrom -Count 1 -Quiet))) {
                Write-Verbose "Neighbour $SendFrom down"
                return $null
            }

            $WinRM = Get-Service -ComputerName $SendFrom -Name WinRM

            If ($WinRM.Status -eq 'Stopped') { $WinRM.Start() }

            If (Test-WSMan -ComputerName $SendFrom -ErrorAction SilentlyContinue) {
                Return $SendFrom
            }
            Else {
                Write-Warning "Neighbour $SendFrom unable to use WinRM"
                return $null
            }
        }

        function Find-WOLProxyOnSubnet($IPSubnet) {
            # Get a machine on $IPSubnet that can be used to send WOL via WinRM
            $MachinesOnSameIP = Get-WmiObject -ComputerName $CfgSiteServer -Namespace root\sms\site_$CfgSiteCode -Query "Select Name from SMS_R_SYSTEM Where IPADDRESSES Like ""$($IPSubnet)"""
            $TotalCountMachinesOnSameIP = $MachinesOnSameIP.Count
            $MachinesOnSameIP = $MachinesOnSameIP.Name | Test-Pingable | ? { $_.Up }
            $MachinesOnSameIP = $MachinesOnSameIP.ComputerName
            write-verbose "Found $($MachinesOnSameIP.Count) pingable computers out of $TotalCountMachinesOnSameIP neighbours"
            $WorkingMachine = $null
            Foreach ($Machine in $MachinesOnSameIP) {
                # Write-Verbose "Testing $Machine.."
                $WorkingMachine = Test-SendFrom -SendFrom $Machine
                if ($WorkingMachine) {
                    Write-Verbose "Can send from $WorkingMachine"
                    break
                }
            }
            return $WorkingMachine   
        }

        function Get-WOLProxyOnSubnet($IPSubnet) {
            # Memoizing wrapper for Find-WOLProxyOnSubnet()
            if ($Proxies[$IPSubnet].Scanned) {
                write-Verbose "$IPSubnet scanned already"
                $WorkingMachine = $Proxies[$IPSubnet].WorkingMachine

                # Double check it still works
                if (Test-SendFrom($WorkingMachine)) {
                    return $WorkingMachine
                    # Otherwise we will rescan subnet again :-(
                }
            }

            write-verbose "Scanning $IPSubnet to find neighbour machine to send broadcast WOL"
            $WorkingMachine = Find-WOLProxyOnSubnet($IPSubnet)
            $Proxies[$IPSubnet] = [PSCustomObject]@{
                Scanned        = $True
                WorkingMachine = $WorkingMachine
            }                
            return $WorkingMachine   
        }

        function Get-IPFromSCCM ($Computer) {
            $Query = "Select IPAddresses from SMS_R_System Where SMS_R_System.Name = '$Computer' AND SMS_R_System.Active = '1'"
            $IPResults = Get-WmiObject -ComputerName $CfgSiteServer -Namespace Root\SMS\Site_$CfgSiteCode -Query $Query
            $IPV4Addr = ""
            ForEach ($IPDest in $IPResults.ipaddresses) {
                $ParsedIP = [System.Net.IPAddress]::Parse($IPDest)
                If ($ParsedIP.AddressFamily -eq 'InterNetwork') {
                    $IPV4Addr = $ParsedIP.IPAddressToString
                }
            }
            Return $IPV4Addr
        }

        # Main
        If (! $BroadCast -and ! $SendFrom){
            # Admin rights is only relevant for local packets. Skip admin check if Broadcast is specified
            if (! (Test-CurrentAdminRights)) { Write-Warning "Please run as Admin"; return }
        }
        If ($SendFrom) {
            $WinRMHost = Test-SendFrom -SendFrom $SendFrom
        }

        If ($PSBoundParameters.ContainsKey('ComputerName')) {
            Foreach ($Computer in $ComputerName) {
                #write-host "`nSending WOL to $Computer"
                If ($BroadCast) {
                    #Get the IP Address/Subnet of a machine (assuming 24 bit netmask)
                    try {
                        $IPOctets = @()
                        $IPOctets = ([System.Net.DNS]::GetHostByName($Computer)).AddressList.IPAddressToString.Split('.')
                    } catch {
                        Write-Verbose "DNS unable to resolve $Computer."
                        $IPV4Addr = Get-IPFromSCCM($Computer)
                        If ($IPV4Addr) {
                            write-verbose "SCCM has IPv4 address of $IPV4Addr recorded for $Computer"
                            $IPOctets = $IPV4Addr -split "\."
                        } else {
                            write-verbose "SCCM also has no recorded IPv4 address for $Computer. I give up on this one."
                            Continue
                        }
                    }
                    $IPSubnet = "$($IPOctets[0]).$($IPOctets[1]).$($IPOctets[2]).%"
                    $Proxy = Get-WOLProxyOnSubnet($IPSubnet)
                    If ($Proxy) {
                        ForEach ($Port in $Ports) {
                            Send-WolWorker -Name $Computer -Port $Port -From $Proxy -BroadCast
                        }
                    } Else {
                        Write-Verbose -Message "No usable machines on same subnet were found to forward WOL. soz."
                    }
                } Else {
                    ForEach ($Port in $Ports) {
                        Send-WolWorker -Name $Computer -Port $Port -From $WinRMHost
                    }
                }
            }
        } Else {
            # Probs can be removed
            ForEach ($Port in $Ports) {
                If ($MacAddress) {
                    Send-WolWorker -MacAddress $MacAddress -Port $Port -From $WinRMHost
                }
                Else {
                    Send-WolWorker -Name $ComputerName -Port $Port -From $WinRMHost
                }
            }
        }
    }
}

function Test-Pingable {
    <#
    .SYNOPSIS
    Parallel pingerer. Promptly pings a plethora of pooters in parallel. Returns a list of computers and whether they are up or not.
    
    .PARAMETER ComputerName
    Computer name to ping

    .EXAMPLE
    Test-pingable PC12ABC
    Get-CfgCollectionMembers "Lab DG35" | Test-Pingable
    
    .NOTES
    Author: Darryl Rees
    Date Created: 24 November 2017
    ChangeLog:
    #>


    Param (
        [Alias("Name","Computer")][Parameter(ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][string[]]$ComputerName
    )

    Begin {
        $jobs=@()
        $Computers=@()
    }
    Process {
        $NumPings = 3
        $jobs = $jobs + (test-connection $ComputerName -count $NumPings -asjob)
        $Computers = $Computers + $ComputerName
    }
    End {
        $pingresults = $jobs | receive-job -wait | select address, responsetime | group -ashash -asstring address
        # Now put back into the original order they were passed, and
        # check to see if a single echo/ping request returned with a non-null responsetime
        Foreach ($Computer in $Computers) {
            [PSCustomObject]@{
                ComputerName = $Computer
                Up = (($PingResults.$Computer | measure-object -property responsetime -maximum).maximum -ne $Null)
            }
        }
    }
}


function Get-CfgIPAddress {
<#
    .SYNOPSIS
    Uses Get-CfgClientInventory to quickly return IP addresses for a specific computer.
    
    .DESCRIPTION
    A shortcut to 'Get-CfgClientInventory -ComputerName xxxxxxx -Property IPAddresses | Select-Object -Property IPAddresses
    
    .PARAMETER ComputerName
    The name of a ConfigMgr client, registered with the Site Server.
    
   .EXAMPLE
    C:\PS>Get-CfgIPAddress 6WMPSN1
    169.254.71.251
    172.16.7.30
    192.168.201.1
    203.57.189.153
    fe80::954a:bf66:6607:4206
    fe80::b1af:461b:efa:176
    fe80::b83a:7f75:cfcc:47fb
    fe80::fda2:bc20:ea38:6c80
    
    .EXAMPLE
    Get-CfgCollectionMembers -Collection "Lab DG40" | Get-CfgIPAddress
        Gets the IPAddresses of members of collection Lab DG40
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

param(
      [Parameter(ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName="$env:computername")
BEGIN{
    Test-CfgGlobalVars -ErrorAction Stop 
}
PROCESS {
    
    function Get-CfgIPWorker {
        Param($Name)
        (Get-CfgClientInventory -ComputerName $Name -Property IPAddresses).IPAddresses
    }

If ($PSBoundParameters.ContainsKey('ComputerName')) {
      Foreach ($Computer in $ComputerName) {
        Get-CfgIPWorker -Name $Computer
      } 
    } Else {
        Get-CfgIPWorker -Name $ComputerName
    }
  }
}

function Send-RepairCCM {
<#
    .SYNOPSIS
    Attempts to repair a ConfigMgr client by sending a WMI method which uses MSI repair function.
    
    .DESCRIPTION
    Connects to the specified machines WMI namespace and runs a repair. If the Force parameter is used, the client is fully uninstalled and re-installed and the WMI repository is rebuilt.
    
    .PARAMETER ComputerName
    The hostname of a computer where you can connect and have administrator privileges.
    
    .PARAMETER Force
    Causes the client to be uninstalled, WMI service stopped, WMI repository renamed, WMI restarted and client re-installed.
    
   .EXAMPLE
    C:\PS>Send-RepairCCM 6wmpsn1
    Uninstalling SCCM Client...
    Success
    Force option: Rebuilding WMI
    Stopping WMI
    Renaming Repository
    Renamed \\9k9562s\c$\windows\Syswow64\wbem\Repository
    Renamed \\9k9562s\c$\windows\system32\wbem\Repository
    Restarting WMI
    SharedAccess would not start
    CCMExec gone. re-installing...
    Success
    
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 09 Jan 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName,[Switch]$Force)
PROCESS {

      function RepairCCM-Worker {
        Param($Name)
        If ($Force) {    
            
            function Send-RemoteCommand {
                Param($Command,$Arguements,$ComputerName,[Switch]$NoWait)
                If ($NoWait) { $Options = "-d" }
                psexec.exe \\$ComputerName -s "$Command" $Arguements $Options 2>Null
                If ($LASTEXITCODE -eq 0) {Write-Host -ForegroundColor Green "Success" } Else {Write-Host -ForegroundColor Red "Fail"; return 1}
            }
            function Test-Service {
                Param($ServiceName,$ComputerName,$Action)
                $Service = Get-Service -ComputerName $ComputerName -Name $ServiceName
                If ( $Action -eq "Stop" ) {If ($Service.Status -eq "Running") {Try {$Service.Stop()} Catch {"$($Service.Name) would not stop"}}}
                If ( $Action -eq "Start") {If ($Service.Status -eq "Stopped") {Try {$Service.Start()} Catch {"$($Service.Name) would not start"}}}
            }
                

            #Setup Commands
            $InstallCommand = "\\wsp-configmgr01\SMS_SC1\Client\ccmsetup.exe /mp:wsp-configmgr01.usc.internal /force SMSSITECODE=SC1 SMSSLP=wsp-configmgr01.usc.internal DNSSUFFIX=USC.INTERNAL SMSMP=wsp-configmgr01.usc.internal"
            $UninstallCommand = "\\wsp-configmgr01\SMS_SC1\Client\ccmsetup.exe /Uninstall"
            #Get Architechture
            
                $SYS = "System32"
                $CCMSetup = "ccmsetup\Logs\ccmsetup.log"
        

            #Uninstall Client
            Write-Host "Uninstalling SCCM Client..."
            Send-RemoteCommand -ComputerName $Name -Command "C:\Windows\$SYS\cmd.exe" -Arguements "/c $UninstallCommand"

            Write-Host "Force option: Rebuilding WMI"
            Write-Host "Stopping WMI"
            Test-Service -ServiceName SharedAccess -ComputerName $Name -Action Stop
            Test-Service -ServiceName winmgmt -ComputerName $Name -Action Stop
            Start-Sleep -Seconds 10
            Write-Host "Renaming Repository"
            $Repository = "\\$Name\c$\windows\Syswow64\wbem\Repository","\\$Name\c$\windows\system32\wbem\Repository"
            Foreach ( $Repo in $Repository ) {
                If (Test-Path $Repo) { Rename-Item -Path $Repo -NewName "Repo.Old"; Write-Host "Renamed $Repo" }
            }
            Write-Host "Restarting WMI"
            Test-Service -ServiceName winmgmt -ComputerName $Name -Action Start
            Test-Service -ServiceName SharedAccess -ComputerName $Name -Action Start
            Start-Sleep -Seconds 20

            Write-Host "CCMExec gone. re-installing..."
            Send-RemoteCommand -ComputerName $Name -NoWait -Command "C:\Windows\$SYS\cmd.exe" -Arguements "/c $InstallCommand"
            Start-Sleep -Seconds 10
            Start-Process cmtrace.exe \\$Name\c$\Windows\$CCMSetup
            While (!(Test-Path \\$Name\c$\Windows\ccm\logs\)) {
                Write-Host "Waiting for ccmexec to come online"
                Start-Sleep -Seconds 10
            }
            Start-Process \\$Name\c$\Windows\ccm\logs\
        } Else {
            $Client = [WMIClass]"\\$($Name)\root\CCM:SMS_Client"
            $Client.InvokeMethod("RepairClient","")
        }
      }

If ($PSBoundParameters.ContainsKey('ComputerName')) {
      Foreach ($Computer in $ComputerName) {
        RepairCCM-Worker -Name $Computer
      } 
    } Else {
        RepairCCM-Worker -Name $ComputerName
    }
  }
}

function Install-CCM {
<#
    .SYNOPSIS
    Attempts to install ConfigMgr client by using PSEXEC.
    
    .DESCRIPTION
    Uses PSExec to connect to a machine and run ccmsetup with USC parameters.
    
    .PARAMETER ComputerName
    The hostname of a computer where you can connect and have administrator privileges.
    
    .PARAMETER Uninstall
    Causes the client to be uninstalled.
    
   .EXAMPLE
    C:\PS>Install-CCM 9k9562s

    PsExec v1.98 - Execute processes remotely
    Copyright (C) 2001-2010 Mark Russinovich
    Sysinternals - www.sysinternals.com

    C:\Windows\SysWOW64\cmd.exe exited on 9k9562s with error code 0.
    Success
    
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 10 April 2012
    ChangeLog:
    1.0 - First Release
#>

  [CmdletBinding()]

      Param(
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
      [string[]]$ComputerName,[Switch]$Uninstall,[Switch]$Install)
PROCESS {

      function InstallCCM-Worker {
        Param($Name)
        
           
            $InstallCommand = "\\wsp-configmgr01\SMS_SC1\Client\ccmsetup.exe /mp:wsp-configmgr01.usc.internal /forceinstall SMSSITECODE=SC1 SMSSLP=wsp-configmgr01.usc.internal DNSSUFFIX=USC.INTERNAL SMSMP=wsp-configmgr01.usc.internal"
            $UninstallCommand = "\\wsp-configmgr01\SMS_SC1\Client\ccmsetup.exe /Uninstall"
            function Send-RemoteCommand {
                Param($Command,$Arguements,$ComputerName,[Switch]$NoWait)
                If ($NoWait) { $Options = "-d" }
                psexec.exe \\$ComputerName -s "$Command" $Arguements $Options
                If ($LASTEXITCODE -eq 0) {Write-Host -ForegroundColor Green "Success" } Else {Write-Host -ForegroundColor Red "Fail"; return 1}
            }
            #Get Architechture
            If ( ( Test-Path -path \\$Name\c$\Windows\Syswow64) ) {
                $SYS = "SysWOW64"
                $CCMSetup = "ccmsetup\Logs\ccmsetup.log"
            } Else {
                $SYS = "System32"
                $CCMSetup = "ccmsetup\Logs\ccmsetup.log"
            }
            If ($Uninstall) {
                Write-Host "Uninstalling SCCM Client..."
                Send-RemoteCommand -ComputerName $Name -Command "C:\Windows\$SYS\cmd.exe" -Arguements "/c $UninstallCommand"
            } else {
                Send-RemoteCommand -ComputerName $Name -NoWait -Command "C:\Windows\$SYS\cmd.exe" -Arguements "/c $InstallCommand"
                Start-Sleep -Seconds 10
                Start-Process cmtrace.exe \\$Name\c$\Windows\$CCMSetup
                While (!(Test-Path \\$Name\c$\Windows\ccm\logs\)) {
                    Write-Host "Waiting for ccmexec to come online"
                    Start-Sleep -Seconds 10
                }
                Start-Process \\$Name\c$\Windows\ccm\logs\
            }
      }

If ($PSBoundParameters.ContainsKey('ComputerName')) {
      Foreach ($Computer in $ComputerName) {
        InstallCCM-Worker -Name $Computer
      } 
    } Else {
        InstallCCM-Worker -Name $ComputerName
    }
  }
}

if (-Not (Get-Alias -Name ginv -ErrorAction SilentlyContinue)) {
    New-Alias -Name ginv -Value Get-CfgClientInventory -Scope Global
}
if (-Not (Get-Alias -Name gip -ErrorAction SilentlyContinue)) {
    New-Alias -Name gip -Value Get-CfgIPAddress -Scope Global
}

function Get-RecentMachines {
    Param($CollectionName,$AgentTimeSpan)
    
    $DaysAgo = (Get-Date).AddDays(-$AgentTimeSpan)
    Get-CfgCollectionMembers -Collection $CollectionName | `
        Get-CfgClientInventory | `
            ForEach-Object {
                $Index = [array]::IndexOf($_.AgentName,"Heartbeat Discovery")
                If (($Index -gt -1) -and ([datetime]::ParseExact($_.AgentTime[$Index],"yyyyMMddHHmmss.000000+***",$null) -gt $DaysAgo)) { 
                    $_ | Select-Object @{label='ComputerName';expression={$_.Name}},@{label='Domain';expression={$_.ResourceDomainORWorkgroup}},LastLogonUserName,IPAddresses,@{label='AgentIndex';expression={$Index}},@{label='AgentTime';expression={Get Date $_.AgentTime[$Index]}}
                }
            }
}

function Get-AdvertisementResult {
<#
    .SYNOPSIS
    Retreive Status of an/all Advertisment(s) from the SCCM primary site server for a specfic computer.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for advertisement status.
    
    .PARAMETER AdvertID
    The ID of an Advertisment on the SCCM Site. Format example: USC20746

    .PARAMETER ComputerName
    The name of a computer to query against.
    
   .EXAMPLE
    C:\PS>Get-AdvertisementResults -ComputerName B1HM52S
    
    ComputerName : B1HM52S
    AdvertisementID : USC20662
    Status : Retrying
    LastStatusTime : 20130418230349.480000+***
    AdvertisementName : Visual3D_Reader - [Virtual application] to Application Tester User

    ComputerName : B1HM52S
    AdvertisementID : USC2071E
    Status : Retrying
    LastStatusTime : 20130418230349.450000+***
    AdvertisementName : XPanels - [Virtual application] to Crestron XPanels 1.0 USR

    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers "Lab HG31" | Get-AdvertisementResults
    Command is usefull for gathering the overall success/failure of advertisements in a venue.
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 20 June 2013
    ChangeLog:
    1.0 - First Release
#>

[CmdLetBinding()]
    Param(
        [Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true,
        Mandatory=$true)]$ComputerName,
        $AdvertID)
    Begin {
        Test-CfgGlobalVars -ErrorAction Stop
    }
    Process {
        If ($ComputerName.ComputerName) {
            $Name = $ComputerName.ComputerName
        } Else {
            $Name = $ComputerName
        }

        Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query `
            "Select ResourceID from SMS_R_System Where Name like '$Name'" | ForEach-Object {
                $ResourceID = $_.ResourceID
                If ($AdvertID) {
                    $Query = "Select AdvertisementID,LastStateName,LastStatusTime From SMS_ClientAdvertisementStatus Where ResourceID = '$ResourceID' and AdvertisementID = '$AdvertID'"
                } Else {
                    $Query = "Select AdvertisementID,LastStateName,LastStatusTime From SMS_ClientAdvertisementStatus Where ResourceID = '$ResourceID'"
                }
                $AdvObj = Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $Query | 
                    Select @{LABEL='ComputerName'; Expression={$Name}},AdvertisementID,@{LABEL='Status'; Expression={$_.LastStateName}},LastStatusTime 
                ForEach ($Adv in $AdvObj) {
                    $AdvID = $Adv.AdvertisementID
                    $AdvName = (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query "Select AdvertisementName From SMS_Advertisement Where AdvertisementID = '$AdvID'").AdvertisementName
                    $Adv | Add-Member -MemberType NoteProperty -Name AdvertisementName -Value $AdvName -PassThru
                }
            }
    }
}


function Get-MachineInventory {
<#
    .SYNOPSIS
    Retreive Inventory information of a USC computer from ActiveDirectory and Config Manager.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for client inventory, Connects to AD and gathers group data and description.
    
    .PARAMETER ComputerName
    The name of a client computer, registered with the Site Server.
    
   .EXAMPLE
    C:\PS>Get-MachineInventory 6WMPSN1
    
    LastedLogonUserName : jpharris
    ADCreated : 6/05/2011 1:01:47 PM
    ADPath : usc.internal/MOEDev/DevWorkstations/Staff/6WMPSN1
    DockStatus :
    ADIPAddress :
    ADMemberOf :
    OU : STAFF
    MonitorRes :
    ComputerName : 6wmpsn1
    MonitorCount :
    Model : Virtual Machine
    ADDescription : M6500 - JPHarris
                          SetBy Jpharris
    WKSGROUP :
    VLAN : Virtual Server (Test / Dev Network)
    ADPWDLastSet : 17/09/2012 9:17:38 AM
    LastHeartbeat : 5/10/2012 8:52:07 AM
    Memory : 1,606 MB
    ADOperatingSystem : Windows 7 Enterprise
   
    .EXAMPLE
    C:\PS>Import-CSV C:\Computers.csv | ForEach-Object { Get-MachineInventory -ComputerName $_."Column A" } | Export-CSV C:\ComputerInventory.csv

    This command will get the machine inventory for each computer in the column titled "Column A" from the Computers.csv file.

    .EXAMPLE
    C:\PS>Get-MachineInventory 6WMPSN1 | Select ComputerName,DockStatus
        Returns just the computername and dockstatus properties
    
    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers -Collection "Latitude E Series" | Get-MachineInventory
        Returns the machine inventory for all computers in the SCCM collection "Latitude E Series"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 05 Oct 2012
    ChangeLog:
    1.0 - First Release
#>

    [CmdLetBinding()]
    Param(
        [Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true,
        Mandatory=$true)]$ComputerName,
        $AdvertID)

    Begin {
        Test-CfgGlobalVars -ErrorAction Stop
        #Check if SCCM Module is available and loaded
        #Write-Host -ForegroundColor Cyan "Checking modules..."
        $Modules = "ActiveDirectory","USC-SCCM","USC-DellWarranty","Import-Excel","USC-VLAN"
        ForEach ($Module in $Modules) {
            If (!(Get-Module -Name $Module)) {
                If (Get-Module -ListAvailable | Where-Object {$_.Name -eq $Module}) {
                    Write-Host -ForegroundColor Cyan "Loading $Module module..."
                    Import-Module $Module
                } Else {
                    Write-Host -ForegroundColor Red "$Module module not available"
                    Return 1
                }
            }
        }
    }

    Process {

        function Get-MachineInventoryWorker {
            Param($Name)
            If ($SCCMInventory = Get-CfgClientInventory -ComputerName $Name -Properties Monitor,Model,Memory) {
                $SCCMInventory.SystemGroupName | Where-Object {(($_ -notmatch "SCCM") -and ($_ -notmatch "Domain Computers")) -or ($_ -match "MGSProd WKS")} | 
                    ForEach-Object {$WKSGroup += $($_ -replace "USC\\*","") + " "}
                $Index = [array]::IndexOf($SCCMInventory.AgentName,"Heartbeat Discovery")
                If ($Index -gt -1) {
                    $HeartBeatDate = [datetime]::ParseExact($SCCMInventory.AgentTime[$Index],"yyyyMMddHHmmss.000000+***",$null)
                }
            }
            If ($ADInventory = Get-ADComputer -Identity $Name -Properties MemberOf,Description,Created,CanonicalName,PasswordLastSet,OperatingSystem,IPv4Address) {
                $ADInventory.MemberOf | Where-Object {($_ -notmatch "SCCM")} | ForEach-Object {$ADGroups += $($_) + " "}
            }
            If ($SCCMInventory.LastLogonUserName) { $ADUser = Get-ADUser -Identity $SCCMInventory.LastLogonUserName -Properties ExtensionAttribute14,mail,DistinguishedName,memberof }

            New-Object -TypeName PSObject -Property @{
                'ComputerName' = $Name
                'MonitorCount' = $SCCMInventory.MonitorCount
                'MonitorRes' = $SCCMInventory.MonitorRes
                'Memory' = $SCCMInventory.Memory
                'LastLogonUserName' = $SCCMInventory.LastLogonUserName
                'LastHeartbeat' = $HeartBeatDate
                'WarrantyEndDate' = (Get-DellWarrantyStatus -ComputerName $Name).WarrantyEndDate
                'OU' = $SCCMInventory.SystemOUName[$SCCMInventory.SystemOUName.Count-1] -replace ".*/",""
                'WKSGROUP' = $WKSGroup
                'Model' = $SCCMInventory.Model
                'ADMemberOf' = $ADGroups
                'ADDescription' = $ADInventory.Description
                'ADCreated' = $ADInventory.Created
                'ADPath' = $ADInventory.CAnonicalName
                'ADPWDLastSet' = $ADInventory.PasswordLastSet
                'ADOperatingSystem' = $ADInventory.OperatingSystem
                'ADIPAddress' = $ADInventory.IPv4Address
                'VLAN' = (Get-VLANFromIPAddress -IPAddress $ADInventory.IPv4Address)
                'UserExtensionAttribute14' = $ADUser.Extensionattribute14
                'UserMail' = $ADUser.mail
                'UserPath' = $ADUser.DistinguishedName
                'UserGroupMembers' = $ADUser.memberof
            }
        }
        If ($PSBoundParameters.ContainsKey('ComputerName')) {
            Foreach ($Computer in $ComputerName) {
                If ($Computer.ComputerName) {
                    Get-MachineInventoryWorker -Name $Computer.ComputerName
                } Else {
                    Get-MachineInventoryWorker -Name $Computer
                }
            }
        } Else {
            Get-MachineInventoryWorker -Name $ComputerName
        }
    }
}

function Get-AdvertisementStatus {
<#
    .SYNOPSIS
    Retreive Status of an Advertisment from the SCCM primary site server.
    
    .DESCRIPTION
    Connects to the primary site server and queries the WMI namespace for advertisement status.
    
    .PARAMETER AdvertID
    The ID of an Advertisment on the SCCM Site. Format example: USC20746
    
   .EXAMPLE
    C:\PS>Get-AdvertisementStatus USC20746
    
    ComputerName LastStateName
    ------------ -------------
    8KLF6R1 Retrying
    7QNQ12S Failed
    GBRS62S Failed
    4K9562S Failed
    HCQ6FS1 Failed
    4WHNBS1 Failed
    DTT5D2S Failed
   
    .EXAMPLE
    C:\PS>Import-CSV C:\Computers.csv | ForEach-Object { Get-MachineInventory -ComputerName $_."Column A" } | Export-CSV C:\ComputerInventory.csv

    This command will get the machine inventory for each computer in the column titled "Column A" from the Computers.csv file.

    .EXAMPLE
    C:\PS>Get-MachineInventory 6WMPSN1 | Select ComputerName,DockStatus
        Returns just the computername and dockstatus properties
    
    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers -Collection "Latitude E Series" | Get-MachineInventory
        Returns the machine inventory for all computers in the SCCM collection "Latitude E Series"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 05 Oct 2012
    ChangeLog:
    1.0 - First Release
#>

[CmdLetBinding()]
    Param(
        [Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true,
        Mandatory=$true)]$AdvertID="USC20746",
        $State)
    Begin {
        Test-CfgGlobalVars -ErrorAction Stop
    }
    Process {
            switch ($State) {
                Failed {
                    $Query = "Select ResourceID,LastStateName From SMS_ClientAdvertisementStatus Where AdvertisementID = '$AdvertID' and LastStateName = 'Failed'"
                    }
                Succeeded {
                    $Query = "Select ResourceID,LastStateName From SMS_ClientAdvertisementStatus Where AdvertisementID = '$AdvertID' and LastStateName = 'Succeeded'"
                    }
                NoStatus {
                    $Query = "Select ResourceID,LastStateName From SMS_ClientAdvertisementStatus Where AdvertisementID = '$AdvertID' and LastStateName = 'No Status'"
                    }
                Accepted {
                    $Query = "Select ResourceID,LastStateName From SMS_ClientAdvertisementStatus Where AdvertisementID = '$AdvertID' and LastStateName LIKE 'Accepted%'"
                    }
                default {
                    $Query = "Select ResourceID,LastStateName From SMS_ClientAdvertisementStatus Where AdvertisementID = '$AdvertID'"
                }
            }

            
            Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query $Query |
                Select @{LABEL='ComputerName'; Expression={$Resource = $_.ResourceID; (Get-WMIObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$($CfgSiteCode)" -Query "Select Name from SMS_R_System Where ResourceID = '$Resource'").Name}},LastStateName
    }
}

function Get-CurrentUser {
    Param(
        [Parameter(
                ValueFromPipeLine = $true,
                    ValueFromPipelinebyPropertyName = $True)]
        $ComputerName=$env:ComputerName)
    Process {
        If ($ComputerName.ComputerName) {
            $Computers = $ComputerName.ComputerName
        } Else {
            $Computers = $ComputerName
        }
        Foreach ($ComputerName in $Computers) {
            If (Test-Connection -ComputerName $ComputerName -Quiet -Count 1) {
                Get-WmiObject -ComputerName $ComputerName -Class Win32_computersystem | 
                    Select -Property @{label='UserName'; expression={$_.UserName.TrimStart("USC\")}},@{label='ComputerName';expression={$ComputerName}}
            }
        }
    }
}

function Invoke-CfgConfigEval {
<#
    .SYNOPSIS
    Evaluate Configuration baslines assigned to a configuration manager client.
    
    .DESCRIPTION
    Connects to a client machine WMI namespace and executes a method on a named configuration item or all configuration itmes.
    
    .PARAMETER ComputerName
    The name of a client computer, with Configuration Manager client installed.
    
   .EXAMPLE
    C:\PS>Invoke-CfgConfigEval D8MN52S
    
    __GENUS : 1
    __CLASS : __PARAMETERS
    __SUPERCLASS :
    __DYNASTY : __PARAMETERS
    __RELPATH : __PARAMETERS
    __PROPERTY_COUNT : 2
    __DERIVATION : {}
    __SERVER : D8MN52S
    __NAMESPACE : ROOT\ccm\dcm
    __PATH : \\D8MN52S\ROOT\ccm\dcm:__PARAMETERS
    JobId : {12BF6F7D-B533-4361-83C3-6B407F07A83F}
    ReturnValue : 0
    PSComputerName : D8MN52S
   
    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers "Lab H107" | Invoke-CfgConfigEval

    This command will retrieve all members of the collection "Lab H107" and attempt to connect to each machines WMI to evaluation configuration baselines.

    .EXAMPLE
    C:\PS>Invoke-CfgConfigEval -ComputerName BSYQXY1 -Name Application-Shortcut-NVR
        Attempts to evaluate only baseline "Application-Shortcut-NVR"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 01 May 2014
    ChangeLog:
    1.0 - First Release
#>

    Param([Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true,
        Mandatory=$true)]$ComputerName=$env:COMPUTERNAME,
        [Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$True)]$DisplayName)

    Process {
        function Config-Worker {
        Param($Computer)
            
            If ($DisplayName.DisplayName) {
                $DisplayName = $DisplayName.DisplayName
            }
            $DCMTrigger = [WMIClass]"\\$Computer\Root\CCM\DCM:SMS_DesiredConfiguration"
            If ($DisplayName) {
                $Query = 'Select * from SMS_DesiredConfiguration where DisplayName = "' + $DisplayName + '"'
            } Else {
                $Query = 'Select * from SMS_DesiredConfiguration'
            }

            $Configurations = Get-WmiObject -ComputerName $Computer -Namespace Root\CCM\DCM -Query $Query
            ForEach ($Config in $Configurations) {
            $Config.Name
            $Config.Version
                $DCMTrigger.TriggerEvaluation($Config.Name,$Config.Version)
            }
        }

        If ($PSBoundParameters.ContainsKey('ComputerName')) {
            Foreach ($Computer in $ComputerName) {
                If ($Computer.ComputerName) {
                    Config-Worker -Computer $Computer.ComputerName
                } Else {
                    Config-Worker -Computer $Computer
                }
            }
        } Else {
            Config-Worker -Computer $ComputerName
        }
    }
}

function Get-CfgConfigEval {
<#
    .SYNOPSIS
    Get Configuration baselines assigned to a configuration manager client.
    
    .DESCRIPTION
    Connects to a client machine WMI namespace and retreives a configuration item or all configuration itmes.
    
    .PARAMETER ComputerName
    The name of a client computer, with Configuration Manager client installed.

    .PARAMETER DisplayName
    The Displayname of a configuration item
    
   .EXAMPLE
    C:\PS>Get-CfgConfigEval D8MN52S
    
    ComputerName : DCG2GY1
    DisplayName : Application-Setting-Flash AutoUpdateDisable
    IsMachineTarget : True
    LastEvalTime : 3/06/2014 12:39:59 AM
    LastComplianceStatus : 1
    Status : 0
    Version : 4

    ComputerName : DCG2GY1
    DisplayName : Application-Presence-Adobe Acrobat 10
    IsMachineTarget : True
    LastEvalTime : 3/06/2014 12:48:18 AM
    LastComplianceStatus : 1
    Status : 0
    Version : 2

    .EXAMPLE
    C:\PS>Get-CfgCollectionMembers "Lab H107" | Get-CfgConfigEval

    This command will retrieve all members of the collection "Lab H107" and attempt to connect to each machines WMI to list configuration baselines.

    .EXAMPLE
    C:\PS>Get-CfgConfigEval -ComputerName BSYQXY1 -DisplayName Application-Shortcut-NVR
        Attempts to list only baseline "Application-Shortcut-NVR"
        
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 06 June 2014
    ChangeLog:
    1.0 - First Release
#>

    Param([Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true,
        Mandatory=$true)]$ComputerName=$env:COMPUTERNAME,$DisplayName)

    Process {
        function Config-Worker {
        Param($Computer)
            If ($DisplayName) {
                $Qeury = "Select * from SMS_DesiredConfiguration where DisplayName = ""$DisplayName"""
            } Else {
                $Qeury = "Select * from SMS_DesiredConfiguration"
            }
            Get-WmiObject -ComputerName $Computer -Namespace Root\CCM\DCM -Query $Qeury | 
                ForEach-Object {
                    Switch ($_.LastComplianceStatus) {
                        0 {$LastComplianceStatus = 'Non-Compliant'}
                        1 {$LastComplianceStatus = 'Compliant'}
                        Default {$LastComplianceStatus = 'Error'}
                    }
                    [pscustomobject]@{
                        'ComputerName' = $_.PSComputerName
                        'DisplayName' = $_.DisplayName
                        'IsMachineTarget' = $_.IsMachineTarget
                        'LastEvalTime' = If ($_.LastEvalTime) {
                            #[datetime]::ParseExact($_.LastEvalTime,'yyyyMMddHHmmss.000000+000',$null)
                            $_.ConvertToDateTime($_.LastEvalTime)                    
                        } Else {
                            $null
                        }
                        'LastComplianceStatus' = $LastComplianceStatus
                        'Status' = $_.Status
                        'Version' = $_.Version
                    }
                }
        }

        If ($PSBoundParameters.ContainsKey('ComputerName')) {
            Foreach ($Computer in $ComputerName) {
                If ($Computer.ComputerName) {
                    Config-Worker -Computer $Computer.ComputerName
                } Else {
                    Config-Worker -Computer $Computer
                }
            }
        } Else {
            Config-Worker -Computer $ComputerName
        }
    }
}

Function Get-CfgItemsByFolder {
<#
    .SYNOPSIS
    Get objects based on their administrative assigned folder.
    
    .DESCRIPTION
    Connects to the primary site server, and returns items which are contained in a folder.
    
    .PARAMETER FolderName
    The name of a folder containing objects. If not specified, a list of folders is returned.

    .PARAMETER ItemType
    Specify the type of item to return folders for.

   .EXAMPLE
    C:\PS> Get-CfgItemsByFolder -FolderName 'software distribution' -ItemType DeviceCollection
    
    FolderName CollectionName CollectionID LimitingCollection
    ---------- -------------- ------------ ------------------
    Software Distribution Adobe Presenter 8 MSI WKS-Uninstall SC100015 All Systems
    Software Distribution Climsystems Trainclim 2.0.0.31 MSI WKS-Install SC10001A Climsystems Trainclim 2.0.0.31 ms..
    Software Distribution Google Google Chrome 23.0.1271.97 MSI WKS SC10001C All Systems
    
    .EXAMPLE
    C:\PS> Get-CfgItemsByFolder -FolderName 'Software Distribution' | ? LimitingCollection -eq "All USC Managed Computers" | %{Set-CMDeviceCollection -CollectionID $_.CollectionID -LimitToCollectionID SC100030

    This command will retrieve all collections under the 'Software Distribution' folder which are currently limited to collection name 'All USC Managed Computers' and limit them to 'All USC Non-Volatile Computers'
        
    .EXAMPLE
    C:\PS> Get-CfgItemsbyFolder -Foldername 'Software Distribution' -ItemType UserCollection

    This command will retrieve user collections by user foldername.

    .EXAMPLE
    C:\PS> Get-CfgItemsByFolder -ItemType TaskSequence

    FolderName
    ----------
    Production
    Firmware Updates
    Retired
    Utility
    Backups
    Kiosks
    Development

    .NOTES
    Some of the examples in this help, depend on the official configuration manager module
    Author: Jesse Harris
    For: University Of Sunshine Coast
    Date Created: 17 May 2016
    Changelog:
    1.0 - First Release
    2.0 - 31/10/2018, Revamp to support more than collections.
#>

[CmdLetBinding()]
param([ValidateSet(
    "Package",
    "DeviceCollection",
    "UserCollection",
    "Application",
    "TaskSequence",
    "Query",
    "ConfigurationBaseline",
    "ConfigurationItem",
    "MeteringRule"
     )]
    [Parameter(Mandatory=$True)]$ItemType,
    $FolderName
    )
    Test-CfgGlobalVars -ErrorAction Stop
    $NS = "root\sms\site_$CfgSiteCode"
    
    $objecttype = Switch ($ItemType) {
        "Package" { "SMS_Package" }
        "DeviceCollection" { "SMS_Collection_Device" }
        "UserCollection" { "SMS_Collection_User" }
        "Application" { "SMS_ApplicationLatest" }
        "TaskSequence" { "SMS_TaskSequencePackage" }
        "Query" { "SMS_Query" }
        "ConfigurationBaseline" { "SMS_ConfigurationBaselineInfo" }
        "ConfigurationItem" { "SMS_ConfigurationItemLatest" }
        "MeteringRule" { "SMS_MeteredProductRule" }
    }

    $q = "select * from sms_objectcontainernode where objecttypename = '$objecttype'"
    If ($FolderName) {
        $InstanceKey = Get-WmiObject -ComputerName $CfgSiteServer -NameSpace $NS -query $q |
        Where-Object {$_.Name -eq $FolderName}
    } else {
        Get-WmiObject -ComputerName $CfgSiteServer -NameSpace $NS -query $q |
        Select-Object -Property @{Name='FolderName';expression={$_.Name}}
    }

    If (-Not $InstanceKey) {
        #Write-Error -Category objectnotfound -Message "No configuration manager folder could be found"
        return
    }

    $q = "select * from sms_objectcontaineritem where containernodeid = "
    $q += "'$($instancekey.containernodeid)'" 
    Get-WmiObject -ComputerName $CfgSiteServer -NameSpace $NS -Query $q | ForEach-Object {
        If ($ItemType -match 'collection') {
            $q = "select * from SMS_Collection where CollectionID = ""$($_.instancekey)"""
            Get-WmiObject -ComputerName $CfgSiteServer -Namespace $NS -Query $q |
            ForEach-Object {
                [pscustomobject]@{
                    'foldername' = $foldername; 
                    'collectionname' = $_.name;
                    'collectionid' = $_.collectionid
                    'limitingcollection' = $_.limittocollectionname
                }
            }
        } else {
            [PSCustomObject]@{
                'FolderName' = $FolderName
                'ObjectID' = $_.InstanceKey
            }
        }
    }
}

Function Get-CfgCollectionsByFolder {
    [CmdLetBinding()]
    Param([Switch]$UserCollection,[Parameter(Mandatory=$True)]$FolderName)

    Write-Warning "This function will be depricated"
    If ($UserCollection) {
        Get-CfgItemsByFolder -ItemType 'UserCollection' -FolderName $FolderName
    } else {
        Get-CfgItemsByFolder -ItemType 'DeviceCollection' -FolderName $FolderName
    }
}

function Get-CfgCollectionsDeps {
<#
    .SYNOPSIS
    Get a list of collections which are limited to a specific collection.
    
    .DESCRIPTION
    Connects to the primary site server WMI namespace and retreives a values from SMS_CollectionDependancies.
    
    .PARAMETER CollectionName
    The name of a source collection to which other collections are limited to.

    .PARAMETER SiteServer
    The hostname of the primary site server
    
   .EXAMPLE
    C:\PS>Get-CfgCollectionsDeps 'All USC Managed Computers'
    
    SourceCollectionID DependentCollectionID
    ------------------ ---------------------
    SC10025C SC100030
    SC10025C SC100413
    SC10025C SC100414
    SC10025C SC100444
    SC10025C SC100500
    SC10025C SC10051C

    .EXAMPLE
    C:\PS>Get-CfgCollectionDeps "All USC Managed Computers" | ForEach-Object {Set-CMDeviceCollection -Id $_.DependentCollectionID -LimitToCollectionId SC100030 -WhatIf}

    This command will update the limiting collection of all collections currently limited to 'All USC Managed Computers'
            
    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 17 May 2016
    ChangeLog:
    1.0 - First Release
#>

[CmdLetBinding()]
Param([Parameter(Mandatory=$True)]$CollectionName)
    Test-CfgGlobalVars -ErrorAction Stop
    
    $CollectionID = (Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_$CfgSiteCode" -Query "Select CollectionID from SMS_Collection Where Name = '$CollectionName'").CollectionID
    If (-Not $CollectionID) {
        Write-Error -Category ObjectNotFound -Message "No collection with name $CollectionName could be found"
        return
    }
    $CollectionDeps = Get-WmiObject -ComputerName $CfgSiteServer -Namespace "root\sms\site_SC1" -Query "select * from SMS_CollectionDependencies where SourceCollectionID='$CollectionID'"
    If (-Not $CollectionDeps) {
        Write-Error -Category ObjectNotFound -Message "No collection references of $CollectionName could be found"
        return
    }

    #Select distinct SourceCollectionID, DependentCollectionID from SMS_CollectionDependencies where SourceCollectionID='SC10047B'

    $CollectionDeps | Select SourceCollectionID,DependentCollectionID
}

function Invoke-CfgStateMessageSend {
    [CmdLetBinding()]
    Param($ComputerName)
    Begin{}
    Process {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock {
            $CCMObject = New-Object -ComObject Microsoft.CCM.UpdatesStore
            $CCMObject.RefreshServerComplianceState()
        }
    }
}

function Set-CfgService {
    [CmdLetBinding()]
    Param($ComputerName=$env:ComputerName,$Name,
    $Previous)
    
    Switch ($Name) {
        rw {$s = 'WinRM'}
        rr {$s = 'RemoteRegistry'}
        cm {$s = 'CCMExec'}
        Default {$s = $Name}
    }

    $Result = $null
    $Svc = Get-Service -ComputerName $ComputerName -Name $s
    If ($Previous -eq 'Disabled') {
            $Svc | Stop-Service
            $Svc | Set-Service -StartUpType 'Disabled'
    ElseIf ($Previous -eq 'Stopped') {
            $Svc | Stop-Service
        }
    } Else {
        Switch ($Svc.Status) {
            'Running' {$Svc | Restart-Service}
            'Stopped' {If ($Svc.StartType -eq 'Disabled') {$Result='Disabled'; $Svc | Set-Service -StartUpType Manual} Else {$Result='Stopped'}; $Svc | Start-Service}
            Default {}
        }
    }
    return $Result
}

function Get-CfgEvalStateDescription {
    # https://msdn.microsoft.com/en-us/library/jj874279.aspx
    Param($EvaluationState)

    Switch ($EvaluationState) {
        0 {'No state information is available.'}
        1 {'Application is enforced to desired/resolved state.'}
        2 {'Application is not required on the client.'}
        3 {'Application is available for enforcement (install or uninstall based on resolved state). Content may/may not have been downloaded.'}
        4 {'Application last failed to enforce (install/uninstall).'}
        5 {'Application is currently waiting for content download to complete.'}
        6 {'Application is currently waiting for content download to complete.'}
        7 {'Application is currently waiting for its dependencies to download.'}
        8 {'Application is currently waiting for a service (maintenance) window.'}
        9 {'Application is currently waiting for a previously pending reboot.'}
        10 {'Application is currently waiting for serialized enforcement.'}
        11 {'Application is currently enforcing dependencies.'}
        12 {'Application is currently enforcing.'}
        13 {'Application install/uninstall enforced and soft reboot is pending.'}
        14 {'Application installed/uninstalled and hard reboot is pending.'}
        15 {'Update is available but pending installation.'}
        16 {'Application failed to evaluate.'}
        17 {'Application is currently waiting for an active user session to enforce.'}
        18 {'Application is currently waiting for all users to logoff.'}
        19 {'Application is currently waiting for a user logon.'}
        20 {'Application in progress, waiting for retry.'}
        21 {'Application is waiting for presentation mode to be switched off.'}
        22 {'Application is pre-downloading content (downloading outside of install job).'}
        23 {'Application is pre-downloading dependent content (downloading outside of install job).'}
        24 {'Application download failed (downloading during install job).'}
        25 {'Application pre-downloading failed (downloading outside of install job).'}
        26 {'Download success (downloading during install job).'}
        27 {'Post-enforce evaluation.'}
        28 {'Waiting for network connectivity.'}
    }
}

function Get-CfgApplicationState {
    [CmdLetBinding()]
    Param(
        [Parameter(
            Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True)]$ComputerName=$env:computername,
        $AppName
    )
    
        Begin {
            $defaultProperties = @('ComputerName','AppName','InstallState','State')
            $defaultDisplayPropertySet = 
                New-Object System.Management.Automation.PSPropertySet(
                    'DefaultDisplayPropertySet',[string[]]$defaultProperties)
            $PSStandardMembers = 
                [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        }

    Process {
        If ($ComputerName.ComputerName) {
            $ComputerName = $Computer.ComputerName
        }
        $WinRMState = Set-CfgService -ComputerName $ComputerName -Name WinRM
        $AppObj = Get-CimInstance -ComputerName $ComputerName `
            -NameSpace root\ccm\ClientSDK -Query $Query
        if ($AppName) {
            $AppObj = $AppObj | Where-Object { $psItem.FullName -match $AppName }
        }
        $AppObj | ForEach-Object {
            $psItem | Add-Member `
                -MemberType NoteProperty `
                -Name State `
                -Value (Get-CfgEvalStateDescription $psItem.EvaluationState) 
            $psItem | Add-Member `
                -MemberType MemberSet `
                -Name PSStandardMembers $PSStandardMembers `
                -PassThru
        }
    }
}