SCCMStuff.psm1

function Add-CMDeviceToCollection {
    <#
    .SYNOPSIS
    Function for easy adding of device(s) to SCCM collection.
 
    .DESCRIPTION
    Function for easy adding of device(s) to SCCM collection.
    It can be added using static or query rule type.
 
    .PARAMETER computerName
    Computer name(s).
 
    .PARAMETER collectionName
    Name of the SCCM collection.
 
    .PARAMETER asQuery
    Switch for adding computer using query rule (instead of static).
    Query rule add computer even after it was deleted and re-added to SCCM database.
 
    .EXAMPLE
    Add-CMDeviceToCollection -computerName ae-79-pc -collectionName 'windows 10 deploy' -asQuery
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)

                Get-ADComputer -Filter "name -like '*$WordToComplete*' -and enabled -eq 'true'" -property Name, Enabled | select -exp Name | sort
            })]
        [string[]] $computerName,

        [Parameter(Mandatory = $true)]
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)

                Get-CMCollection -Name "*$WordToComplete*" | select -exp Name | sort | % { "'$_'" }
            })]
        [string] $collectionName,

        [switch] $asQuery
    )

    Connect-SCCM -ea Stop

    if (!(Get-CMCollection -Name $collectionName)) {
        throw "Collection '$collectionName' doesn't exist"
    }

    # get computer resourceId
    $computerHash = @{}
    $computerName | % {
        if (Get-CMCollectionMember -CollectionName $collectionName -Name $_) {
            Write-Warning "$_ is already in collection '$collectionName'. Skipping"
        } else {
            $computerId = Get-CMDevice -Name $_ -Fast | select -exp ResourceId
            if ($computerId) {
                $computerHash.$_ = $computerId
            } else {
                Write-Warning "Computer $_ wasn't found in SCCM database"
            }
        }
    }

    if ($computerHash.Keys) {
        if ($asQuery) {
            # add query rule (will survive computers removal from SCCM database)
            $computerHash.GetEnumerator() | % {
                Add-CMDeviceCollectionQueryMembershipRule -CollectionName $collectionName -QueryExpression "select SMS_R_System.ResourceId from SMS_R_System where SMS_R_System.Name = `"$($_.key)`"" -RuleName ($_.key).toupper()
            }
        } else {
            # add static rule
            $computerHash.GetEnumerator() | % {
                Add-CMDeviceCollectionDirectMembershipRule -CollectionName $collectionName -ResourceId $_.value
            }
        }

        # update membership
        Invoke-CMCollectionUpdate -Name $collectionName -Confirm:$false
    } else {
        Write-Warning "No such computer was found in SCCM database"
    }
}

function Clear-CMClientCache {
    <#
    .SYNOPSIS
        vymaze cache SCCM klienta (persistentni balicky ponecha)
    .DESCRIPTION
        vymaze cache SCCM klienta (persistentni balicky ponecha)
        druha varianta https://gallery.technet.microsoft.com/scriptcenter/Deleting-the-SCCM-Cache-da03e4c7
    #>


    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipeline = $True, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = "localhost"
    )

    PROCESS {
        Invoke-Command2 -ComputerName $ComputerName -ScriptBlock {
            $Computer = $env:COMPUTERNAME

            if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
                throw "You don't have administrator rights"
            }

            Try {
                #Connect to Resource Manager COM Object
                $resman = New-Object -com "UIResource.UIResourceMgr"
                $cacheInfo = $resman.GetCacheInfo()

                #Enum Cache elements, compare date, and delete older than 60 days
                $cacheinfo.GetCacheElements() | foreach { $cacheInfo.DeleteCacheElement($_.CacheElementID) }
                if ($?) {
                    Write-Output "$computer hotovo"
                }

            } catch {
                Write-Output "$computer error"
            }
        }
    }
}

function Connect-SCCM {
    <#
    .SYNOPSIS
    Helper function for making session to SCCM server, to be able to call locally any available command from SCCM module there.
 
    .DESCRIPTION
    Helper function for making session to SCCM server, to be able to call locally any available command from SCCM module there.
 
    .PARAMETER sccmServer
    Name of your SCCM server.
 
    .PARAMETER commandName
    (Optional)
 
    Name of command(s) you want to import instead of all.
 
    .EXAMPLE
    Connect-SCCM -sccmServer SCCM-01
    #>


    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        $sccmServer = $_SCCMServer
        ,
        [string[]]$commandName
    )

    $correctlyConfSession = ""
    $sessionExist = Get-PSSession | ? { $_.computername -eq $sccmServer -and $_.state -eq "opened" }

    # remove broken sessions
    Get-PSSession | ? { $_.computername -eq $sccmServer -and $_.state -eq "broken" } | Remove-PSSession

    if ($commandName) {
        # check that pssession already exists and contains given commands
        $commandExist = try {
            Get-Command $commandName -ErrorAction Stop
        } catch {}

        if ($sessionExist -and $commandExist) {
            $correctlyConfSession = 1
            Write-Verbose "Session to $sccmServer is already created and contains required commands"
        }
    } else {
        # check that pssession already exists and that number of commands there is more than 50 (it is highly probable, that session contains all available commands)
        if ($sessionExist -and ((Get-Command -ListImported | ? { $_.name -like "*-cm*" -and $_.source -like "tmp_*" }).count -gt 50)) {
            $correctlyConfSession = 1
            Write-Verbose "Session to $sccmServer is already created"
        }
    }

    if (!$correctlyConfSession) {
        if (Test-Connection $sccmServer -ErrorAction SilentlyContinue) {
            # pssession doesn't contain necessary commands
            try {
                Write-Verbose "Removing existing sessions that doesn't contain required commands"
                Get-PSSession | ? { $_.computername -eq $sccmServer } | Remove-PSSession
            } catch {}

            $sccmSession = New-PSSession -ComputerName $sccmServer -Name "SCCM"

            try {
                $ErrorActionPreference = "stop"
                Invoke-Command -Session $sccmSession -ScriptBlock {
                    $ErrorActionPreference = "stop"

                    try {
                        Import-Module "$(Split-Path $Env:SMS_ADMIN_UI_PATH)\ConfigurationManager.psd1"
                    } catch {
                        throw "Unable to import SCCM module on $env:COMPUTERNAME"
                    }

                    try {
                        $sccmSite = (Get-PSDrive -PSProvider CMSite).name
                        Set-Location -Path ($sccmSite + ":\")
                    } catch {
                        throw "Unable to retrieve SCCM Site Code"
                    }
                }

                $Params = @{
                    'session'      = $sccmSession
                    'Module'       = 'ConfigurationManager'
                    'AllowClobber' = $true
                    'ErrorAction'  = "Stop"
                }
                if ($commandName) {
                    $Params.Add("CommandName", $CommandName)
                }

                # import-module is used, so the commands will be available even if Connect-SCCM is called from module
                Import-Module (Import-PSSession @Params) -Global -Force
            } catch {
                "To be able to use SCCM commands remotely you have to:`n1. Connect to $sccmServer using RDP.`n2. Run SCCM console under account, that should use commands remotely.`n3. In SCCM console run PowerShell console (Connect via PowerShell).`n4. In such PowerShell console enable import of certificate by selecting choice '[A] Always run'"

                "Second option should be to:`n1. Open file properties of 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'.`n2. On tab Digital Signatures > Details > View Certificate > Install Certificate > Install such certificate to Trusted Publishers store"

                "Error was: $($_.Exception.Message)"
            }
        } else {
            "$sccmServer is offline"
        }
    }
}

function Get-CMAppDeploymentStatus {
    <#
    .SYNOPSIS
        Skript vraci stav nainstalovanosti deploymentu aplikaci na zadanych strojich.
    .DESCRIPTION
        Skript vraci stav nainstalovanosti deploymentu aplikaci na zadanych strojich.
        Nevraci SW, ktery neni nainstalovan z duvodu nesplneni pozadavku na instalaci
    .PARAMETER Computername
        Jmeno stroje, ze ktereho chci vysledky.
    .PARAMETER ApplicationName
        Vyfiltrovani vysledku dle 'Localized Application DisplayName' nazvu aplikace (zjistim v konzoli u dane aplikace na zalozce Application Catalog).
    .PARAMETER NotInstalled
        Zobrazi pouze nenainstalovane aplikace
    .PARAMETER targeted
        Zobrazeni aplikaci cilenych per user/computer.
        Vychozi jsou i per user i per computer.
    .PARAMETER Install
        Spusti instalaci vsech nalezenych nenainstalovanych aplikaci.
    .PARAMETER SiteServer
        Your SCCM site server
    .PARAMETER SiteCode
        The 3 character SCCM site code
    .EXAMPLE
        PS> Get-CmAppDeploymentStatus -ApplicationName "BIOS Update"
 
        Vrati stav deploymentu aplikace "BIOS Update" na lokalnim klientovi.
    #>


    [CmdletBinding()]
    param (
        [string[]] $Computername = $env:COMPUTERNAME
        ,
        [string] $ApplicationName
        ,
        [switch] $NotInstalled
        ,
        [ValidateSet('perUser', 'perComputer')]
        [string[]] $targeted = ("perUser", "perComputer")
        ,
        [switch] $Install
        ,
        [ValidateNotNullOrEmpty()]
        [string] $SiteServer = $_SCCMServer
        ,
        [ValidateNotNullOrEmpty()]
        [string] $SiteCode = $_SCCMSiteCode
    )

    begin {
        if ($NotInstalled) {
            $status = "Installed"
        } else {
            $status = "fakefakefake"
        }

        $EvalStates = @{
            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';
        }

        # translate human readable state to its ID
        $wantedEvalState = @()
        if ($targeted -contains "perComputer") { $wantedEvalState += 1 }
        if ($targeted -contains "perUser") { $wantedEvalState += 2 }
    }

    process {
        foreach ($Computer in $Computername) {
            if ($targeted -contains "perUser" -and $Computer -eq $env:COMPUTERNAME) {
                Write-Warning "Per-User applications for only $env:username will be shown"
            } else {
                Write-Warning "Per-User applications won't be shown (I don't know how)"
            }

            $Params = @{
                'Computername' = $Computer
                'Namespace'    = 'root\ccm\clientsdk'
                'Class'        = 'CCM_Application'
            }

            if ($ApplicationName) {
                $Applications = Get-CimInstance @Params | Where-Object { $_.FullName -like "*$ApplicationName*" -and $_.InstallState -notlike $status -and $_.EvaluationState -in $wantedEvalState }
            } else {
                $Applications = Get-CimInstance @Params | Where-Object { $_.InstallState -notlike $status -and $_.EvaluationState -in $wantedEvalState }
            }

            if ($Applications) {
                $Applications | Select-Object PSComputerName, Name, InstallState, SoftwareVersion, ErrorCode, @{ n = 'EvalState'; e = { $EvalStates[[int]$_.EvaluationState] } }, @{ label = 'ApplicationMadeAvailable'; expression = { $_.ConvertToDateTime($_.StartTime) } }, @{ label = 'LastInstallDate'; expression = { $_.ConvertToDateTime($_.LastInstallTime) } }
            } elseif (!$NotInstalled) {
                "Application is not installed on $Computer."
            }

            if ($install) {
                $ApplicationClass = [WmiClass]"\\$Computer\root\ccm\clientSDK:CCM_Application"
                # vynutim spusteni instalace (jen u nenainstalovanych)
                $Applications | Where-Object { InstallState -NotLike "installed" } | % {
                    $Application = $_
                    $ApplicationID = $Application.Id
                    $ApplicationRevision = $Application.Revision
                    $ApplicationIsMachineTarget = $Application.ismachinetarget
                    $EnforcePreference = "Immediate"
                    $Priority = "high"
                    $IsRebootIfNeeded = $false

                    Write-Verbose "Starting installation of $($application.fullname)"
                    $null = $ApplicationClass.Install($ApplicationID, $ApplicationRevision, $ApplicationIsMachineTarget, 0, $Priority, $IsRebootIfNeeded)
                    # spravne poradi parametru ziskam pomoci $ApplicationClass.GetMethodParameters("install") | select -first 1 | select -exp properties
                    #Invoke-CimMethod -ComputerName titan02 -Class $Params.Class -Namespace $Params.Namespace -Name install -ArgumentList 0,$ApplicationID,$ApplicationIsMachineTarget,$IsRebootIfNeeded,$Priority,$ApplicationRevision
                }
            }
        }
    }
}

function Get-CMApplicationOGV {
    param (
        [ValidateNotNullOrEmpty()]
        $sccmServer = $_SCCMServer
        ,
        [ValidateNotNullOrEmpty()]
        $siteCode = $_SCCMSiteCode
        ,
        $title = "Vyber aplikaci"
    )

    Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query 'SELECT * FROM SMS_Application WHERE isexpired="false" AND isenabled="true"' |
    select LocalizedDisplayName, LocalizedDescription, SoftwareVersion, NumberOfDeployments, NumberOfDevicesWithApp, NumberOfDevicesWithFailure |
    sort LocalizedDisplayName | ogv -OutputMode Multiple -Title $title | select -exp LocalizedDisplayName
}

function Get-CMAutopilotHash {
    <#
    .SYNOPSIS
    Function for getting Autopilot hash from SCCM database.
 
    .DESCRIPTION
    Function for getting Autopilot hash from SCCM database.
    Hash is by default gathered during Hardware inventory cycle.
 
    .PARAMETER computerName
    Name of the computer you want to get the hash for.
    If omitted, hashes for all computers will be returned.
 
    .PARAMETER SCCMServer
    Name of the SCCM server.
 
    .PARAMETER SCCMSiteCode
    SCCM site code
 
    .EXAMPLE
    Get-CMAutopilotHash -computerName "PC-01" -SCCMServer "CMServer" -SCCMSiteCode "XXX"
 
    Get Autopilot hash for PC-01 computer from SCCM server.
 
    .EXAMPLE
    Get-CMAutopilotHash -SCCMServer "CMServer" -SCCMSiteCode "XXX"
 
    Get Autopilot hash for all computers from SCCM server.
 
    .NOTES
    Requires function Invoke-SQL and permission to read data from SCCM database.
    #>


    [CmdletBinding()]
    param (
        [string[]] $computerName,

        [ValidateNotNullOrEmpty()]
        $SCCMServer = $_SCCMServer,

        [ValidateNotNullOrEmpty()]
        $SCCMSiteCode = $_SCCMSiteCode
    )

    $sql = @'
SELECT
distinct(bios.SerialNumber0) as "SerialNumber",
System.Name0 as "Hostname",
User_Name0 as "Owner",
(osinfo.SerialNumber0) as "WindowsProductID",
mdminfo.DeviceHardwareData0 as "HardwareHash"
FROM v_R_System System
Inner Join v_GS_PC_BIOS bios on System.ResourceID=bios.ResourceID
Inner Join v_GS_OPERATING_SYSTEM osinfo on System.ResourceID=osinfo.ResourceID
Inner Join v_GS_MDM_DEVDETAIL_EXT01 mdminfo on System.ResourceID=mdminfo.ResourceID
'@


    if ($computerName) {
        $sql = "$sql WHERE System.Name0 IN ($(($computerName | % {"'"+$_+"'"}) -join ", "))"
        Write-Verbose $sql
    }

    Invoke-SQL -dataSource $SCCMServer -database "CM_$SCCMSiteCode" -sqlCommand $sql
}

function Get-CMCollectionComplianceStatus {
    [CmdletBinding()]
    param (
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
                Get-CimInstance -Namespace "root\SMS\Site_$_SCCMSiteCode" -Query "select LocalizedDisplayName from SMS_ConfigurationBaselineInfo" -ComputerName $_SCCMServer | ? { $_.LocalizedDisplayName -like "*$WordToComplete*" } | % { '"' + $_.LocalizedDisplayName + '"' }
            })]
        [string[]] $confBaseline
        ,
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
                Get-CimInstance -Namespace "root\SMS\Site_$_SCCMSiteCode" -Query "select Name from SMS_Collection" -ComputerName $_SCCMServer | ? { $_.Name -like "*$WordToComplete*" } | % { '"' + $_.Name + '"' }
            })]
        [string[]] $collection
        ,
        [string[]] $computerName
    )

    if ($computerName -and $collection) {
        Write-Warning "Collection will be ignored, because you have selected computerName"
    }
    if (!$confBaseline -and !$computerName -and !$collection) {
        throw "You have to specify confBaseline and/or collection and/or computer"
    }

    $filter = ""

    if ($confBaseline) {
        $list = @()
        $confBaseline | % {
            $name = $_
            $list += Get-CimInstance -Namespace "root\SMS\Site_$_SCCMSiteCode" -Query "select LocalizedDisplayName, CI_ID from SMS_ConfigurationBaselineInfo" -ComputerName $_SCCMServer | ? { $_.LocalizedDisplayName -eq $name } | select -exp CI_ID
        }

        if ($filter) {
            $and = " and"
        }
        $list = $list -join ', '
        $filter += "$and CI.CI_ID IN($list)"
    }

    if ($computerName) {
        if ($filter) {
            $and = " and"
        }
        $computerName = ($computerName | % { "'" + $_ + "'" }) -join ', '
        $filter += "$and VRS.Netbios_Name0 IN($computerName)"
    }

    if ($collection) {
        $list = @()
        $collection | % {
            $name = $_
            $list += Get-CimInstance -Namespace "root\SMS\Site_$_SCCMSiteCode" -Query "select Name, CollectionID from SMS_Collection" -ComputerName $_SCCMServer | ? { $_.Name -eq $name } | select -exp CollectionID
        }

        if ($filter) {
            $and = " and"
        }
        $list = ($list | % { "'" + $_ + "'" }) -join ', '
        $filter += "$and FM.CollectionID IN ($list)"
    }

    $sqlCommand = "
    select distinct VRS.Netbios_Name0 as ComputerName, CI.UserName, CI.DisplayName, CI.ComplianceStateName from v_R_System VRS
    right join v_FullCollectionMembership_Valid FM on VRS.ResourceID=FM.ResourceID
    right join fn_ListCI_ComplianceState(1033) CI on VRS.ResourceID=CI.ResourceID
    where $filter"


    Write-Verbose $sqlCommand

    $a = Invoke-SQL -dataSource $_SCCMServer -database "CM_$_SCCMSiteCode" -sqlCommand $sqlCommand
    $a | select ComputerName, UserName, DisplayName, ComplianceStateName | Sort-Object ComputerName
}

function Get-CMCollectionOGV {
    param (
        [ValidateNotNullOrEmpty()]
        [string] $sccmServer = $_SCCMServer
        ,
        [ValidateNotNullOrEmpty()]
        [string] $siteCode = $_SCCMSiteCode
        ,
        [string] $title = "Vyber kolekci"
        ,
        [ValidateSet('Multiple', 'Single')]
        [string] $outputMode = "Multiple"
        ,
        [ValidateSet('user', 'device', 'all')]
        [string[]] $type = "all"
        ,
        [switch] $returnAsObject
    )

    if ($type -eq "user") {
        $collectionType = 1
    } elseif ($type -eq "device") {
        $collectionType = 2
    } else {
        $collectionType = 1, 2
    }
    $collection = Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query 'SELECT * FROM SMS_Collection' | ? { $_.CollectionType -in $collectionType } | select Name, Comment, MemberCount, RefreshType, CollectionID | sort Name | ogv -OutputMode $outputMode -Title $title
    if ($returnAsObject) {
        $collection
    } else {
        $collection | select -exp Name
    }
}

function Get-CMComputerCollection {
    <#
    .SYNOPSIS
    Function returns name of computer's collection(s).
 
    .DESCRIPTION
    Function returns name of computer's collection(s).
 
    .PARAMETER computerName
    Name of computer.
 
    .PARAMETER SCCMServer
    Name of the SCCM server.
 
    Default is $_SCCMServer.
 
    .EXAMPLE
    Get-CMComputerCollection ni-20-ntb
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $computerName,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMServer = $_SCCMServer
    )

    if (!$SCCMServer) { throw "Undefined SCCMServer" }

    (Get-CimInstance -ComputerName $SCCMServer -Namespace root/SMS/site_$_SCCMSiteCode -Query "SELECT SMS_Collection.* FROM SMS_FullCollectionMembership, SMS_Collection where name = '$computerName' and SMS_FullCollectionMembership.CollectionID = SMS_Collection.CollectionID").Name
}

function Get-CMComputerComplianceStatus {
    <#
    .SYNOPSIS
    Function gets status of SCCM compliance baselines on given client.
 
    .DESCRIPTION
    Function gets status of SCCM compliance baselines on given client.
    Shows user and device compliances (thanks to Invoke-AsCurrentUser).
 
    If run locally, returns object with all user (which run this function) and device CB status.
    If run remotely, returns string with all (there logged) user and device CB status.
 
    .PARAMETER computerName
    Name of remote computer to connect.
 
    .PARAMETER onlyComputerCB
    Switch for showing just device targeted CB not user ones.
    But as advantage, object will be returned instead of string.
 
    .EXAMPLE
    Get-CMComputerComplianceStatus
 
    Returns configuration baselines status as object.
    User and device ones.
 
    .EXAMPLE
    Get-CMComputerComplianceStatus -computerName pc-01
 
    Returns configuration baselines status as string.
    User and device ones.
 
    .EXAMPLE
    Get-CMComputerComplianceStatus -computerName pc-01 -onlyComputerCB
 
    Returns configuration baselines status as object. Just device CB ones.
    #>


    [CmdletBinding()]
    param (
        [string] $computerName
        ,
        [switch] $onlyComputerCB
    )

    #region prepare param for Invoke-AsLoggedUser
    $param = @{ReturnTranscript = $true }

    if ($computerName -and $computerName -notmatch "localhost|$env:COMPUTERNAME") {
        $param.computerName = $computerName
    }
    #endregion prepare param for Invoke-AsLoggedUser

    $scriptBlockText = @'
$Baselines = Get-CimInstance -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
ForEach ($Baseline in $Baselines) {
    $bsDisplayName = $Baseline.DisplayName
    $name = $Baseline.Name
    $IsMachineTarget = $Baseline.IsMachineTarget
    $IsEnforced = $Baseline.IsEnforced
    $PolicyType = $Baseline.PolicyType
    $version = $Baseline.Version
 
    switch ($Baseline.LastComplianceStatus) {
        0 { $bsStatus = "Noncompliant" }
        1 { $bsStatus = "Compliant" }
        2 { $bsStatus = "NotApplicable" }
        3 { $bsStatus = "Unknown" }
        4 { $bsStatus = "Error" }
        5 { $bsStatus = "NotEvaluated" }
        default {$bsStatus = "*Unknown*"}
    }
 
    [xml]$ComplianceDetails = $baseline.ComplianceDetails
 
    [PSCustomObject]@{
        DisplayName = $bsDisplayName
        Status = $bsStatus
        LastEvaluated = $Baseline.LastEvalTime
        CI = $ComplianceDetails.ConfigurationItemReport.ReferencedConfigurationItems.ConfigurationItemReport | ? { $_ } | % {
            $property = [ordered]@{
                Name = $_.CIProperties.name.'#text'
                State = $_.CIComplianceState
            }
            $DiscoveryViolations = $_.DiscoveryViolations.DiscoveryViolation.SettingInformation.Errors.Error.ErrorDescription
            if ($DiscoveryViolations) {
                $property.DiscoveryViolations = $DiscoveryViolations
            }
            New-Object -TypeName PSObject -Property $property
        }
        IsMachineTarget = $IsMachineTarget
        Version = $version
    }
}
'@
 # end of scriptBlock text


    $scriptBlock = [Scriptblock]::Create($scriptBlockText)

    if ($param.computerName) {
        if ($onlyComputerCB) {
            Invoke-Command -ComputerName $computerName -ScriptBlock $scriptBlock
        } else {
            Invoke-AsLoggedUser -ScriptBlock $scriptBlock @param
        }
    } else {
        Invoke-Command -ScriptBlock $scriptBlock
    }
}

function Get-CMDeploymentStatus {
    <#
    .SYNOPSIS
    Get SCCM (not just application) deployment status.
 
    .DESCRIPTION
    Get SCCM (not just application) deployment status.
 
    .PARAMETER name
    (optional) name of the deployment.
 
    .PARAMETER SCCMServer
    Name of the SCCM server.
 
    Default is $_SCCMServer.
 
    .PARAMETER SCCMSiteCode
    Name of the SCCM site.
 
    Default is $_SCCMSiteCode.
 
    .EXAMPLE
    Get-CMDeploymentStatus
 
    Returns deployment status of all deployments in SCCM.
 
    .EXAMPLE
    Get-CMDeploymentStatus -name CB_not_for_ConditionalAccess
 
    Returns deployment status of CB_not_for_ConditionalAccess compliance deployment.
    #>


    [CmdletBinding()]
    param (
        [string] $name,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMServer = $_SCCMServer,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMSiteCode = $_SCCMSiteCode
    )

    if ($name) {
        $nameFilter = "where SoftwareName = '$name'"
    }

    Get-CimInstance -ComputerName $SCCMServer -Namespace "root\SMS\site_$SCCMSiteCode" -Query "SELECT SoftwareName, CollectionName, NumberTargeted, NumberSuccess, NumberErrors, NumberInprogress, NumberOther, NumberUnknown FROM SMS_DeploymentSummary $nameFilter" | select SoftwareName, CollectionName, NumberTargeted, NumberSuccess, NumberErrors, NumberInprogress, NumberOther, NumberUnknown
}

function Get-CMLog {
    <#
    .SYNOPSIS
    Function for easy opening of SCCM logs.
 
    You have two options to define what log(s) you want to open:
     - by specifying AREA of your problem
     - by specifying NAME of the LOG(S)
 
    .DESCRIPTION
    Function for easy opening of SCCM logs.
 
    You have two options to define what log(s) you want to open:
     - by specifying AREA of your problem
     - by specifying NAME of the LOG(S)
 
    Benefits of using AREA approach:
     - you don't have to remember which logs are for which type of problem
     - you don't have to remember where such logs are stored
 
    Benefits of using LOG NAME approach:
     - you don't have to remember where such logs are stored
 
    General benefits of using this function:
     - description for each log is outputted
      - it is retrieved from https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/log-files#BKMK_ServerLogs and cached locally so ongoing runs will be much faster!
     - function supports opening of archived logs
     - best possible log viewer application will be used
      - Sorted by preference: 'Configuration Manager Support Center Log Viewer', 'Support Center OneTrace', CMTrace or as a last resort in default associated program
 
    How to get the log viewers:
    - 'Configuration Manager Support Center Log Viewer' and 'Support Center OneTrace' can be installed via 'C:\Program Files\Microsoft Configuration Manager\tools\SupportCenter\supportcenterinstaller.msi' (saved on your SCCM server) or by installing SCCM Administrator console.
    - CMTrace is installed by default with SCCM Client
 
    .PARAMETER computerName
    Name of computer where CLIENT logs should be obtained.
    In case the problem is related to SCCM server, this parameter will be ignored.
 
    .PARAMETER area
    What area (problem) you want to show logs from.
 
    Possible values:
    ApplicationDiscovery
    ApplicationDownload
    ApplicationInstallation
    ApplicationManagement
    ApplicationMetering
    AssetIntelligence
    BackupAndRecovery
    BootImageUpdate
    CertificateEnrollment
    ClientInstallation
    ClientNotification
    ClientPush
    CMG
    CMGClientTraffic
    CMGDeployments
    CMGHealth
    Co-Management
    Compliance
    ComplianceSettingsAndCompanyResourceAccess
    ConfigurationManagerConsole
    ContentDistribution
    ContentManagement
    DesktopAnalytics
    Discovery
    EndpointAnalytics
    EndpointProtection
    ExchangeServerConnector
    Extensions
    Inventory
    InventoryProcessing
    Metering
    Migration
    MobileDeviceLegacy
    MobileDevicesEnrollment
    NotificationClient
    NotificationServer
    NotificationServerInstall
    OSDeployment
    OSDeployment_clientPerspective
    PackagesAndPrograms
    PolicyProcessing
    PowerManagement
    PXE
    RemoteControl
    Reporting
    Role-basedAdministration
    SoftwareMetering
    SoftwareUpdates
    WindowsServicing
    WindowsUpdateAgent
    WOL
    WSUSServer
 
    .PARAMETER logName
    Name of the log(s) you want to open.
    Function itself knows where log(s) are stored, so just name is enough.
 
    Possible values:
    ADALOperationProvider, adctrl, ADForestDisc, adminservice, AdminUI.ExtensionInstaller, ADService, adsgdis, adsysdis, adusrdis, aikbmgr, AIUpdateSvc, AIUSMSI, AIUSSetup, AlternateHandler, AppDiscovery, AppEnforce, AppGroupHandler, AppIntentEval, AssetAdvisor, BgbHttpProxy, bgbisapiMSI, bgbmgr, BGBServer, BgbSetup, BitLockerManagementHandler, BusinessAppProcessWorker, CAS, CBS, ccm, CCM_STS, Ccm32BitLauncher, CCMAgent, CCMClient, CcmEval, CcmEvalTask, CcmExec, CcmIsapi, CcmMessaging, CcmNotificationAgent, CCMNotificationAgent, CCMNotifications, ccmperf, Ccmperf, CCMPrefPane, CcmRepair, CcmRestart, Ccmsdkprovider, CCMSDKProvider, ccmsetup, ccmsetup-ccmeval, ccmsqlce, CcmUsrCse, CCMVDIProvider, CertEnrollAgent, CertificateMaintenance, CertMgr, CIAgent, Cidm, CIDownloader, CIStateStore, CIStore, CITaskManager, CITaskMgr, client.msi, client.msi_uninstall, ClientAuth, ClientIDManagerStartup, ClientLocation, ClientServicing, CloudDP, CloudMgr, Cloudusersync, CMBITSManager, CMGContentService, CMGHttpHandler, CMGService, CMGSetup, CMHttpsReadiness, CmRcService, CMRcViewer, CollectionAADGroupSyncWorker, CollEval, colleval, CoManagementHandler, ComplRelayAgent, compmon, compsumm, ComRegSetup, ConfigMgrAdminUISetup, ConfigMgrPrereq, ConfigMgrSetup, ConfigMgrSetupWizard, ContentTransferManager, CreateTSMedia, Crp, Crpctrl, Crpmsi, Crpsetup, dataldr, Dataldr, DataTransferService, DCMAgent, DCMReporting, DcmWmiProvider, ddm, DeltaDownload, despool, Diagnostics, DISM, Dism, dism, distmgr, Distmgr, DmCertEnroll, DMCertResp.htm, DmClientHealth, DmClientRegistration, DmClientSetup, DmClientXfer, DmCommonInstaller, DmInstaller, DmpDatastore, DmpDiscovery, Dmpdownloader, DmpHardware, DmpIsapi, dmpmsi, DMPRP, DMPSetup, DmpSoftware, DmpStatus, dmpuploader, Dmpuploader, DmSvc, DriverCatalog, DWSSMSI, DWSSSetup, easdisc, EndpointConnectivityCheckWorker, EndpointProtectionAgent, enrollmentservice, enrollmentweb, EnrollSrv, enrollsrvMSI, EnrollWeb, enrollwebMSI, EPCtrlMgr, EPMgr, EPSetup, execmgr, ExpressionSolver, ExternalEventAgent, ExternalNotificationsWorker, FeatureExtensionInstaller, FileBITS, FileSystemFile, FspIsapi, fspmgr, fspMSI, FSPStateMessage, hman, Change, chmgr, Inboxast, inboxmgr, inboxmon, InternetProxy, InventoryAgent, InventoryProvider, invproc, loadstate, LocationCache, LocationServices, M365ADeploymentPlanWorker, M365ADeviceHealthWorker, M365AHandler, M365AUploadWorker, MaintenanceCoordinator, ManagedProvider, mcsexec, mcsisapi, mcsmgr, MCSMSI, Mcsperf, mcsprv, MCSSetup, Microsoft.ConfigMgrDataWarehouse, MicrosoftPolicyPlatformSetup.msi, Mifprovider, migmctrl, MP_ClientIDManager, MP_CliReg, MP_Ddr, MP_DriverManager, MP_Framework, MP_GetAuth, MP_GetPolicy, MP_Hinv, MP_Location, MP_OOBMgr, MP_Policy, MP_RegistrationManager, MP_Relay, MP_RelayMsgMgr, MP_Retry, MP_Sinv, MP_SinvCollFile, MP_Status, mpcontrol, mpfdm, mpMSI, MPSetup, mtrmgr, MVLSImport, NDESPlugin, netdisc, NotiCtrl, ntsvrdis, Objreplmgr, objreplmgr, offermgr, offersum, OfflineServicingMgr, outboxmon, outgoingcontentmanager, PatchDownloader, PatchRepair, PerfSetup, PkgXferMgr, PolicyAgent, PolicyAgentProvider, PolicyEvaluator, PolicyPlatformClient, policypv, PolicyPV, PolicySdk, PrestageContent, PullDP, Pwrmgmt, pwrmgmt, PwrProvider, rcmctrl, RebootCoordinator, replmgr, ResourceExplorer, RESTPROVIDERSetup, ruleengine, ScanAgent, scanstate, SCClient, SCNotify, Scripts, SdmAgent, sender, SensorEndpoint, SensorManagedProvider, SensorWmiProvider, ServiceConnectionTool, ServiceWindowManager, SettingsAgent, Setupact, setupact, Setupapi, Setuperr, setuppolicyevaluator, schedule, Scheduler, sinvproc, sitecomp, Sitecomp, sitectrl, sitestat, SleepAgent, smpisapi, Smpmgr, smpmsi, smpperf, SMS_AZUREAD_DISCOVERY_AGENT, SMS_BOOTSTRAP, SMS_BUSINESS_APP_PROCESS_MANAGER, SMS_Cloud_ProxyConnector, SMS_CLOUDCONNECTION, SMS_DataEngine, SMS_DM, SMS_ImplicitUninstall, SMS_ISVUPDATES_SYNCAGENT, SMS_MESSAGE_PROCESSING_ENGINE, SMS_OrchestrationGroup, SMS_PhasedDeployment, SMS_REST_PROVIDER, SmsAdminUI, smsbkup, Smsbkup, SmsClientMethodProvider, smscliui, smsdbmon, SMSdpmon, smsdpprov, smsdpusage, SMSENROLLSRVSetup, SMSENROLLWEBSetup, smsexec, SMSFSPSetup, Smsprov, SMSProv, smspxe, smssmpsetup, smssqlbkup, Smsts, smstsvc, Smswriter, SmsWusHandler, SoftwareCenterSystemTasks, SoftwareDistribution, SrcUpdateMgr, srsrp, srsrpMSI, srsrpsetup, SrvBoot, StateMessage, StateMessageProvider, statesys, Statesys, statmgr, StatusAgent, SUPSetup, swmproc, SWMTRReportGen, TaskSequenceProvider, TSAgent, TSDTHandler, UpdatesDeployment, UpdatesHandler, UpdatesStore, UserAffinity, UserAffinityProvider, UserService, UXAnalyticsUploadWorker, VCRedist_x64_Install, VCRedist_x86_Install, VirtualApp, wakeprxy-install, wakeprxy-uninstall, WCM, Wedmtrace, WindowsUpdate, wolcmgr, wolmgr, WsfbSyncWorker, WSUSCtrl, wsyncmgr, WUAHandler, WUSSyncXML
 
 
    .PARAMETER maxHistory
    How much archived logs you want to see.
    Default is 0.
 
    .PARAMETER SCCMServer
    Name of the SCCM server.
    Needed in case the opened log is stored on the SCCM server, not client.
    To open server side logs admin share (C$) is used, so this function has to be run with appropriate rights.
 
    Default is $_SCCMServer.
 
    .PARAMETER WSUSServer
    Name of the WSUS server.
    Needed in case the opened log is stored on the WSUS server, not client.
 
    If not specified value from SCCMServer parameter will be used.
 
    .PARAMETER serviceConnectionPointServer
    Name of the Service Connection Point server.
    Needed in case the opened log is stored on the Service Connection Point server, not client.
 
    If not specified value from SCCMServer parameter will be used.
 
    .EXAMPLE
    Get-CMLog -area ApplicationDiscovery
 
    Opens all logs on this computer related to application discovery.
 
    .EXAMPLE
    Get-CMLog -area ApplicationDiscovery -maxHistory 3
 
    Opens all logs on this computer related to application discovery. Including archived ones (but at maximum 3 latest for each log).
 
    .EXAMPLE
    Get-CMLog -computerName PC01 -area ApplicationInstallation
 
    Opens all logs on PC01 related to application installation.
 
    .EXAMPLE
    Get-CMLog -logName CcmEval, CcmExec
 
    Opens logs CcmEval, CcmExec.
 
    .EXAMPLE
    Get-CMLog -area PXE -SCCMServer SCCM01
 
    Opens all logs related to PXE. If such logs are stored on SCCM server they will be searched on 'SCCM01'.
 
    .NOTES
    To add new (problem) area:
        - add its name to ValidateSet of $area parameter
        - define what logs should be opened in $areaDetails
        - check $logDetails that it defines path where are these new logs saved
 
    List of all SCCM logs https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/log-files.
    #>


    [CmdletBinding(DefaultParameterSetName = 'area')]
    param (
        [Parameter(Position = 0)]
        [string] $computerName,

        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "area")]
        [ValidateSet('ApplicationDiscovery', 'ApplicationDownload', 'ApplicationInstallation', 'ApplicationManagement', 'ApplicationMetering', 'AssetIntelligence', 'BackupAndRecovery', 'BootImageUpdate', 'CertificateEnrollment', 'ClientInstallation', 'ClientNotification', 'ClientPush', 'CMG', 'CMGClientTraffic', 'CMGDeployments', 'CMGHealth', 'Co-Management', 'Compliance', 'ComplianceSettingsAndCompanyResourceAccess', 'ConfigurationManagerConsole', 'ContentDistribution', 'ContentManagement', 'DesktopAnalytics', 'Discovery', 'EndpointAnalytics', 'EndpointProtection', 'ExchangeServerConnector', 'Extensions', 'Inventory', 'InventoryProcessing', 'Metering', 'Migration', 'MobileDeviceLegacy', 'MobileDevicesEnrollment', 'NotificationClient', 'NotificationServer', 'NotificationServerInstall', 'OSDeployment', 'OSDeployment_clientPerspective', 'PackagesAndPrograms', 'PolicyProcessing', 'PowerManagement', 'PXE', 'RemoteControl', 'Reporting', 'Role-basedAdministration', 'SoftwareMetering', 'SoftwareUpdates', 'WindowsServicing', 'WindowsUpdateAgent', 'WOL', 'WSUSServer')]
        [Alias("problem")]
        [string] $area,

        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "logName")]
        [ValidateSet('ADALOperationProvider', 'adctrl', 'ADForestDisc', 'adminservice', 'AdminUI.ExtensionInstaller', 'ADService', 'adsgdis', 'adsysdis', 'adusrdis', 'aikbmgr', 'AIUpdateSvc', 'AIUSMSI', 'AIUSSetup', 'AlternateHandler', 'AppDiscovery', 'AppEnforce', 'AppGroupHandler', 'AppIntentEval', 'AssetAdvisor', 'BgbHttpProxy', 'bgbisapiMSI', 'bgbmgr', 'BGBServer', 'BgbSetup', 'BitLockerManagementHandler', 'BusinessAppProcessWorker', 'CAS', 'CBS', 'ccm', 'CCM_STS', 'Ccm32BitLauncher', 'CCMAgent', 'CCMClient', 'CcmEval', 'CcmEvalTask', 'CcmExec', 'CcmIsapi', 'CcmMessaging', 'CcmNotificationAgent', 'CCMNotificationAgent', 'CCMNotifications', 'ccmperf', 'Ccmperf', 'CCMPrefPane', 'CcmRepair', 'CcmRestart', 'Ccmsdkprovider', 'CCMSDKProvider', 'ccmsetup', 'ccmsetup-ccmeval', 'ccmsqlce', 'CcmUsrCse', 'CCMVDIProvider', 'CertEnrollAgent', 'CertificateMaintenance', 'CertMgr', 'CIAgent', 'Cidm', 'CIDownloader', 'CIStateStore', 'CIStore', 'CITaskManager', 'CITaskMgr', 'client.msi', 'client.msi_uninstall', 'ClientAuth', 'ClientIDManagerStartup', 'ClientLocation', 'ClientServicing', 'CloudDP', 'CloudMgr', 'Cloudusersync', 'CMBITSManager', 'CMGContentService', 'CMGHttpHandler', 'CMGService', 'CMGSetup', 'CMHttpsReadiness', 'CmRcService', 'CMRcViewer', 'CollectionAADGroupSyncWorker', 'CollEval', 'colleval', 'CoManagementHandler', 'ComplRelayAgent', 'compmon', 'compsumm', 'ComRegSetup', 'ConfigMgrAdminUISetup', 'ConfigMgrPrereq', 'ConfigMgrSetup', 'ConfigMgrSetupWizard', 'ContentTransferManager', 'CreateTSMedia', 'Crp', 'Crpctrl', 'Crpmsi', 'Crpsetup', 'dataldr', 'Dataldr', 'DataTransferService', 'DCMAgent', 'DCMReporting', 'DcmWmiProvider', 'ddm', 'DeltaDownload', 'despool', 'Diagnostics', 'DISM', 'Dism', 'dism', 'distmgr', 'Distmgr', 'DmCertEnroll', 'DMCertResp.htm', 'DmClientHealth', 'DmClientRegistration', 'DmClientSetup', 'DmClientXfer', 'DmCommonInstaller', 'DmInstaller', 'DmpDatastore', 'DmpDiscovery', 'Dmpdownloader', 'DmpHardware', 'DmpIsapi', 'dmpmsi', 'DMPRP', 'DMPSetup', 'DmpSoftware', 'DmpStatus', 'dmpuploader', 'Dmpuploader', 'DmSvc', 'DriverCatalog', 'DWSSMSI', 'DWSSSetup', 'easdisc', 'EndpointConnectivityCheckWorker', 'EndpointProtectionAgent', 'enrollmentservice', 'enrollmentweb', 'EnrollSrv', 'enrollsrvMSI', 'EnrollWeb', 'enrollwebMSI', 'EPCtrlMgr', 'EPMgr', 'EPSetup', 'execmgr', 'ExpressionSolver', 'ExternalEventAgent', 'ExternalNotificationsWorker', 'FeatureExtensionInstaller', 'FileBITS', 'FileSystemFile', 'FspIsapi', 'fspmgr', 'fspMSI', 'FSPStateMessage', 'hman', 'Change', 'chmgr', 'Inboxast', 'inboxmgr', 'inboxmon', 'InternetProxy', 'InventoryAgent', 'InventoryProvider', 'invproc', 'loadstate', 'LocationCache', 'LocationServices', 'M365ADeploymentPlanWorker', 'M365ADeviceHealthWorker', 'M365AHandler', 'M365AUploadWorker', 'MaintenanceCoordinator', 'ManagedProvider', 'mcsexec', 'mcsisapi', 'mcsmgr', 'MCSMSI', 'Mcsperf', 'mcsprv', 'MCSSetup', 'Microsoft.ConfigMgrDataWarehouse', 'MicrosoftPolicyPlatformSetup.msi', 'Mifprovider', 'migmctrl', 'MP_ClientIDManager', 'MP_CliReg', 'MP_Ddr', 'MP_DriverManager', 'MP_Framework', 'MP_GetAuth', 'MP_GetPolicy', 'MP_Hinv', 'MP_Location', 'MP_OOBMgr', 'MP_Policy', 'MP_RegistrationManager', 'MP_Relay', 'MP_RelayMsgMgr', 'MP_Retry', 'MP_Sinv', 'MP_SinvCollFile', 'MP_Status', 'mpcontrol', 'mpfdm', 'mpMSI', 'MPSetup', 'mtrmgr', 'MVLSImport', 'NDESPlugin', 'netdisc', 'NotiCtrl', 'ntsvrdis', 'Objreplmgr', 'objreplmgr', 'offermgr', 'offersum', 'OfflineServicingMgr', 'outboxmon', 'outgoingcontentmanager', 'PatchDownloader', 'PatchRepair', 'PerfSetup', 'PkgXferMgr', 'PolicyAgent', 'PolicyAgentProvider', 'PolicyEvaluator', 'PolicyPlatformClient', 'policypv', 'PolicyPV', 'PolicySdk', 'PrestageContent', 'PullDP', 'Pwrmgmt', 'pwrmgmt', 'PwrProvider', 'rcmctrl', 'RebootCoordinator', 'replmgr', 'ResourceExplorer', 'RESTPROVIDERSetup', 'ruleengine', 'ScanAgent', 'scanstate', 'SCClient', 'SCNotify', 'Scripts', 'SdmAgent', 'sender', 'SensorEndpoint', 'SensorManagedProvider', 'SensorWmiProvider', 'ServiceConnectionTool', 'ServiceWindowManager', 'SettingsAgent', 'Setupact', 'setupact', 'Setupapi', 'Setuperr', 'setuppolicyevaluator', 'schedule', 'Scheduler', 'sinvproc', 'sitecomp', 'Sitecomp', 'sitectrl', 'sitestat', 'SleepAgent', 'smpisapi', 'Smpmgr', 'smpmsi', 'smpperf', 'SMS_AZUREAD_DISCOVERY_AGENT', 'SMS_BOOTSTRAP', 'SMS_BUSINESS_APP_PROCESS_MANAGER', 'SMS_Cloud_ProxyConnector', 'SMS_CLOUDCONNECTION', 'SMS_DataEngine', 'SMS_DM', 'SMS_ImplicitUninstall', 'SMS_ISVUPDATES_SYNCAGENT', 'SMS_MESSAGE_PROCESSING_ENGINE', 'SMS_OrchestrationGroup', 'SMS_PhasedDeployment', 'SMS_REST_PROVIDER', 'SmsAdminUI', 'smsbkup', 'Smsbkup', 'SmsClientMethodProvider', 'smscliui', 'smsdbmon', 'SMSdpmon', 'smsdpprov', 'smsdpusage', 'SMSENROLLSRVSetup', 'SMSENROLLWEBSetup', 'smsexec', 'SMSFSPSetup', 'Smsprov', 'SMSProv', 'smspxe', 'smssmpsetup', 'smssqlbkup', 'Smsts', 'smstsvc', 'Smswriter', 'SmsWusHandler', 'SoftwareCenterSystemTasks', 'SoftwareDistribution', 'SrcUpdateMgr', 'srsrp', 'srsrpMSI', 'srsrpsetup', 'SrvBoot', 'StateMessage', 'StateMessageProvider', 'statesys', 'Statesys', 'statmgr', 'StatusAgent', 'SUPSetup', 'swmproc', 'SWMTRReportGen', 'TaskSequenceProvider', 'TSAgent', 'TSDTHandler', 'UpdatesDeployment', 'UpdatesHandler', 'UpdatesStore', 'UserAffinity', 'UserAffinityProvider', 'UserService', 'UXAnalyticsUploadWorker', 'VCRedist_x64_Install', 'VCRedist_x86_Install', 'VirtualApp', 'wakeprxy-install', 'wakeprxy-uninstall', 'WCM', 'Wedmtrace', 'WindowsUpdate', 'wolcmgr', 'wolmgr', 'WsfbSyncWorker', 'WSUSCtrl', 'wsyncmgr', 'WUAHandler', 'WUSSyncXML')]
        [ValidateScript( {
                If ($_ -match "\.log$") {
                    throw "Enter log name without extension (.log)"
                } else {
                    $true
                }
            })]
        [string[]] $logName,

        [ValidateRange(0, 100)]
        [int] $maxHistory = 0,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMServer = $_SCCMServer,

        [string] $WSUSServer,

        [string] $serviceConnectionPointServer,

        [ValidateScript( {
                If ((Test-Path $_) -and ($_ -match "\.exe$")) {
                    $true
                } else {
                    throw "Enter path to log viewer binary (C:\apps\cmtrace.exe)"
                }
            })]
        [string] $viewer
    )

    #region prepare
    if (!$serviceConnectionPointServer -and $SCCMServer) {
        Write-Verbose "Setting serviceConnectionPointServer parameter to '$SCCMServer'"
        $serviceConnectionPointServer = $SCCMServer
    }

    if (!$WSUSServer -and $SCCMServer) {
        Write-Verbose "Setting WSUSServer parameter to '$SCCMServer'"
        $WSUSServer = $SCCMServer
    }

    #region define common folders where logs are stored
    # client's log location
    if ($computerName) {
        # client's log location
        $clientLog = "\\$computerName\C$\Windows\CCM\Logs"
        # client's setup log location
        $clientSetupLog = "\\$computerName\C$\Windows\ccmsetup\Logs"
        # Remote Control log location (stored on computer that runs Remote Control)
        $remoteControlLog = "\\$computerName\C$\Windows\Temp"
        # SCCM console log location (stored on computer that runs SCCM console)
        $sccmConsoleLog = "\\$computerName\C$\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\AdminUILog"
    } else {
        # client's log location
        $clientLog = "$env:windir\CCM\Logs"
        # client's setup log location
        $clientSetupLog = "$env:windir\ccmsetup\Logs"
        # Remote Control log location (stored on computer that runs Remote Control)
        $remoteControlLog = "$env:windir\Temp"
        # SCCM console log location (stored on computer that runs SCCM console)
        $sccmConsoleLog = "${env:ProgramFiles(x86)}\Microsoft Endpoint Manager\AdminConsole\AdminUILog"
    }
    # client's SMSTS log location
    $clientSMSTSLog = "$clientLog\SMSTSLog"


    # server's log locations
    $serverLog = "\\$SCCMServer\C$\Program Files\SMS_CCM\Logs"
    $serverLog2 = "\\$SCCMServer\C$\Program Files\Microsoft Configuration Manager\Logs"
    $serverDISMLog = "\\$SCCMServer\C$\Windows\Logs\DISM"
    $WSUSLog = "\\$WSUSServer\C$\Program Files\Update Services\LogFiles"

    # Service Connection Point location
    $serviceConnectionPointLog = "\\$serviceConnectionPointServer\C$\Program Files\Configuration Manager\Logs\M365A"
    #endregion define common folders where logs are stored

    #region define where specific logs are stored
    $logDetails = @(
        [PSCustomObject]@{
            logName   = @('AdminUI.ExtensionInstaller', 'ConfigMgrAdminUISetup', 'CreateTSMedia', 'FeatureExtensionInstaller', 'ResourceExplorer', 'SmsAdminUI')
            logFolder = $sccmConsoleLog
        },

        [PSCustomObject]@{
            logName   = @('CMRcViewer')
            logFolder = $remoteControlLog
        },

        [PSCustomObject]@{
            logName   = @('ccmsetup-ccmeval', 'ccmsetup', 'CcmRepair', 'client.msi', 'client.msi_uninstall', 'MicrosoftPolicyPlatformSetup.msi', 'PatchRepair', 'VCRedist_x64_Install', 'VCRedist_x86_Install')
            logFolder = $clientSetupLog
        },

        [PSCustomObject]@{
            logName   = @('ADALOperationProvider', 'BitLockerManagementHandler', 'CAS', 'Ccm32BitLauncher', 'CcmEval', 'CcmEvalTask', 'CcmExec', 'CcmMessaging', 'CCMNotificationAgent', 'Ccmperf', 'CcmRestart', 'CCMSDKProvider', 'ccmsqlce', 'CcmUsrCse', 'CCMVDIProvider', 'CertEnrollAgent', 'CertificateMaintenance', 'CIAgent', 'CIDownloader', 'CIStateStore', 'CIStore', 'CITaskMgr', 'ClientAuth', 'ClientIDManagerStartup', 'ClientLocation', 'ClientServicing', 'CMBITSManager', 'CMHttpsReadiness', 'CmRcService', 'CoManagementHandler', 'ComplRelayAgent', 'ContentTransferManager', 'DataTransferService', 'DCMAgent', 'DCMReporting', 'DcmWmiProvider', 'DeltaDownload', 'Diagnostics', 'EndpointProtectionAgent', 'execmgr', 'ExpressionSolver', 'ExternalEventAgent', 'FileBITS', 'FileSystemFile', 'FSPStateMessage', 'InternetProxy', 'InventoryAgent', 'InventoryProvider', 'LocationCache', 'LocationServices', 'M365AHandler', 'MaintenanceCoordinator', 'Mifprovider', 'mtrmgr', 'PolicyAgent', 'PolicyAgentProvider', 'PolicyEvaluator', 'PolicyPlatformClient', 'PolicySdk', 'Pwrmgmt', 'PwrProvider', 'SCClient', 'Scheduler', 'SCNotify', 'Scripts', 'SensorWmiProvider', 'SensorEndpoint', 'SensorManagedProvider', 'setuppolicyevaluator', 'SleepAgent', 'SmsClientMethodProvider', 'smscliui', 'SrcUpdateMgr', 'StateMessageProvider', 'StatusAgent', 'SWMTRReportGen', 'UserAffinity', 'UserAffinityProvider', 'VirtualApp', 'Wedmtrace', 'wakeprxy-install', 'wakeprxy-uninstall', 'ClientServicing', 'CCMClient', 'CCMAgent', 'CCMNotifications', 'CCMPrefPane', 'AppIntentEval', 'AppDiscovery', 'AppEnforce', 'AppGroupHandler', 'Ccmsdkprovider', 'SettingsAgent', 'SoftwareCenterSystemTasks', 'TSDTHandler', 'execmgr', 'AssetAdvisor', 'BgbHttpProxy', 'CcmNotificationAgent', 'CIAgent', 'CITaskManager', 'DCMAgent', 'DCMReporting', 'DcmWmiProvider', 'M365AHandler', 'InventoryAgent', 'SensorWmiProvider', 'SensorEndpoint', 'SensorManagedProvider', 'EndpointProtectionAgent', 'mtrmgr', 'SWMTRReportGen', 'DmCertEnroll', 'DMCertResp.htm', 'DmClientSetup', 'DmClientXfer', 'DmCommonInstaller', 'DmInstaller', 'DmSvc', 'CAS', 'ccmsetup', 'Setupact', 'Setupapi', 'Setuperr', 'smpisapi', 'TSAgent', 'loadstate', 'scanstate', 'pwrmgmt', 'AlternateHandler', 'ccmperf', 'DeltaDownload', 'PolicyEvaluator', 'RebootCoordinator', 'ScanAgent', 'SdmAgent', 'ServiceWindowManager', 'SmsWusHandler', 'StateMessage', 'UpdatesDeployment', 'UpdatesHandler', 'UpdatesStore', 'WUAHandler', 'CBS', 'DISM', 'setupact', 'WindowsUpdate')
            logFolder = $clientLog
        },

        [PSCustomObject]@{
            logName   = @('Smsts')
            logFolder = $clientSMSTSLog
        },

        [PSCustomObject]@{
            logName   = @('adctrl', 'ADForestDisc', 'adminservice', 'ADService', 'adsgdis', 'adsysdis', 'adusrdis', 'BusinessAppProcessWorker', 'ccm', 'CertMgr', 'chmgr', 'Cidm', 'CollectionAADGroupSyncWorker', 'colleval', 'compmon', 'compsumm', 'ComRegSetup', 'dataldr', 'ddm', 'despool', 'distmgr', 'EPCtrlMgr', 'EPMgr', 'EPSetup', 'EnrollSrv', 'EnrollWeb', 'ExternalNotificationsWorker', 'fspmgr', 'hman', 'Inboxast', 'inboxmgr', 'inboxmon', 'invproc', 'migmctrl', 'mpcontrol', 'mpfdm', 'mpMSI', 'MPSetup', 'netdisc', 'NotiCtrl', 'ntsvrdis', 'Objreplmgr', 'offermgr', 'offersum', 'OfflineServicingMgr', 'outboxmon', 'PerfSetup', 'PkgXferMgr', 'policypv', 'rcmctrl', 'replmgr', 'RESTPROVIDERSetup', 'ruleengine', 'schedule', 'sender', 'sinvproc', 'sitecomp', 'sitectrl', 'sitestat', 'SMS_AZUREAD_DISCOVERY_AGENT', 'SMS_BUSINESS_APP_PROCESS_MANAGER', 'SMS_DataEngine', 'SMS_ISVUPDATES_SYNCAGENT', 'SMS_MESSAGE_PROCESSING_ENGINE', 'SMS_OrchestrationGroup', 'SMS_PhasedDeployment', 'SMS_REST_PROVIDER', 'smsbkup', 'smsdbmon', 'SMSENROLLSRVSetup', 'SMSENROLLWEBSetup', 'smsexec', 'SMSFSPSetup', 'SMSProv', 'srsrpMSI', 'srsrpsetup', 'statesys', 'statmgr', 'swmproc', 'UXAnalyticsUploadWorker', 'ConfigMgrPrereq', 'ConfigMgrSetup', 'ConfigMgrSetupWizard', 'SMS_BOOTSTRAP', 'smstsvc', 'DWSSMSI', 'DWSSSetup', 'Microsoft.ConfigMgrDataWarehouse', 'FspIsapi', 'fspMSI', 'CcmIsapi', 'CCM_STS', 'ClientAuth', 'MP_CliReg', 'MP_Ddr', 'MP_Framework', 'MP_GetAuth', 'MP_GetPolicy', 'MP_Hinv', 'MP_Location', 'MP_OOBMgr', 'MP_Policy', 'MP_RegistrationManager', 'MP_Relay', 'MP_RelayMsgMgr', 'MP_Retry', 'MP_Sinv', 'MP_SinvCollFile', 'MP_Status', 'UserService', 'CollEval', 'Cloudusersync', 'Dataldr', 'Distmgr', 'Dmpdownloader', 'Dmpuploader', 'EndpointConnectivityCheckWorker', 'WsfbSyncWorker', 'objreplmgr', 'PolicyPV', 'outgoingcontentmanager', 'ServiceConnectionTool', 'Sitecomp', 'SMS_CLOUDCONNECTION', 'Smsprov', 'SrvBoot', 'Statesys', 'PatchDownloader', 'SUPSetup', 'WCM', 'WSUSCtrl', 'wsyncmgr', 'WUSSyncXML', 'PrestageContent', 'SMS_ImplicitUninstall', 'SMSdpmon', 'aikbmgr', 'AIUpdateSvc', 'AIUSMSI', 'AIUSSetup', 'ManagedProvider', 'MVLSImport', 'Smsbkup', 'smssqlbkup', 'Smswriter', 'Crp', 'Crpctrl', 'Crpsetup', 'Crpmsi', 'NDESPlugin', 'bgbmgr', 'BGBServer', 'BgbSetup', 'bgbisapiMSI', 'CloudMgr', 'CMGSetup', 'CMGService', 'SMS_Cloud_ProxyConnector', 'CMGContentService', 'CMGHttpHandler', 'CloudDP', 'DataTransferService', 'PullDP', 'smsdpprov', 'smsdpusage', 'M365ADeploymentPlanWorker', 'M365ADeviceHealthWorker', 'M365AUploadWorker', 'DMPRP', 'dmpmsi', 'DMPSetup', 'enrollsrvMSI', 'enrollmentweb', 'enrollwebMSI', 'enrollmentservice', 'SMS_DM', 'easdisc', 'DmClientHealth', 'DmClientRegistration', 'DmpDatastore', 'DmpDiscovery', 'DmpHardware', 'DmpIsapi', 'DmpSoftware', 'DmpStatus', 'Dism', 'DriverCatalog', 'mcsisapi', 'mcsexec', 'mcsmgr', 'mcsprv', 'MCSSetup', 'MCSMSI', 'Mcsperf', 'MP_ClientIDManager', 'MP_DriverManager', 'Smpmgr', 'smpmsi', 'smpperf', 'smspxe', 'smssmpsetup', 'TaskSequenceProvider', 'srsrp', 'mtrmgr', 'wolcmgr', 'wolmgr', 'Change', 'SoftwareDistribution')
            logFolder = $serverLog, $serverLog2
        },

        [PSCustomObject]@{
            logName   = @('dism')
            logFolder = $serverDISMLog
        },

        [PSCustomObject]@{
            logName   = @('Change', 'SoftwareDistribution')
            logFolder = $WSUSLog
        },

        [PSCustomObject]@{
            logName   = @('Cloudusersync', 'Dmpdownloader', 'dmpuploader', 'EndpointConnectivityCheckWorker', 'M365ADeploymentPlanWorker', 'M365ADeviceHealthWorker', 'M365AUploadWorker', 'outgoingcontentmanager', 'SMS_CLOUDCONNECTION', 'SmsAdminUI', 'SrvBoot', 'WsfbSyncWorker')
            logFolder = $serviceConnectionPointLog
        }
    )
    #endregion define where specific logs are stored

    #region get best possible log viewer
    if (!$viewer) {
        $CMLogViewer = "${env:ProgramFiles(x86)}\Microsoft Endpoint Manager\AdminConsole\bin\CMLogViewer.exe"
        $CMLogViewer2 = "${env:ProgramFiles(x86)}\Configuration Manager Support Center\CMLogViewer.exe"
        $CMPowerLogViewer = "${env:ProgramFiles(x86)}\Microsoft Endpoint Manager\AdminConsole\bin\CMPowerLogViewer.exe"
        $CMPowerLogViewer2 = "${env:ProgramFiles(x86)}\Configuration Manager Support Center\CMPowerLogViewer.exe"
        $CMTrace = "$env:windir\CCM\CMTrace.exe"

        if (Test-Path $CMLogViewer) {
            $viewer = $CMLogViewer
        } elseif (Test-Path $CMLogViewer2) {
            $viewer = $CMLogViewer2
        } elseif (Test-Path $CMPowerLogViewer) {
            $viewer = $CMPowerLogViewer
        } elseif (Test-Path $CMPowerLogViewer2) {
            $viewer = $CMPowerLogViewer2
        } elseif (Test-Path $CMTrace) {
            $viewer = $CMTrace
        }
    }
    #endregion get best possible log viewer

    #region helper functions
    function _getAndCacheLogDescription {
        $uri = "https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/log-files"
        Write-Verbose "Getting logs info from $uri"
        try {
            $pageContent = Invoke-WebRequest -Method GET -Uri $uri -ErrorAction Stop
        } catch {
            Write-Warning "Unable to get data from $uri. Description for the logs will not be shown."
            return
        }

        # on page some tables have 'Log Name' as column name and others have just 'Log'
        # also some logs are defined multiple times so remove duplicities
        $script:logDescription = $pageContent.ParsedHtml.getElementsByTagName('table') | % { ConvertFrom-HTMLTable $_ } | select @{n = 'LogName'; e = { if ($_.'Log Name') { ($_.'Log Name' -split "\s+")[0] } else { ($_.'Log' -split "\s+")[0] } } }, @{n = 'Description'; e = { $_.Description } } | sort -Unique -Property LogName

        # cache the results
        Write-Verbose "Caching data to '$cachedLogDescription'"
        $script:logDescription | Export-Clixml -Path $cachedLogDescription -Force
    }

    function _getLogDescription {
        param (
            [Parameter(Mandatory = $true)]
            [string[]] $logName,

            [switch] $secondRun
        )

        $logWithoutDescription = 'CMGHttpHandler', 'client.msi_uninstall'

        if ($script:logDescription) {
            if (!$secondRun) {
                Write-Host "Log(s) description #####################`n" -ForegroundColor Green
            }

            $logName | % {
                $lName = $_.trim()

                # log names on web page can be in these forms too
                # CCMAgent-<date_time>.log
                # SCClient_<domain>@<username>_1.log
                # SleepAgent_<domain>@SYSTEM_0.log
                $wantedLogDescription = $script:logDescription | ? LogName -Match "^$lName\.log$|^$lName[-_].+\.log$"

                if ($wantedLogDescription) {
                    # for better readibility output as string
                    $wantedLogDescription | % {
                        $_.LogName
                        " - " + $_.Description
                        ""
                    }
                } else {
                    if ($secondRun) {
                        Write-Warning "Unable to get description for $lName log."
                    } else {
                        if ($lName -in $logWithoutDescription) {
                            Write-Warning "For $lName there is no description."
                        } else {
                            Write-Warning "Unable to get description for $lName log. Trying to get newest data from Microsoft site"

                            _getAndCacheLogDescription

                            # try again
                            _getLogDescription $lName -secondRun # secondRun parameter to avoid infinite loop
                        }
                    }
                }
            }

            if (!$secondRun) {
                Write-Host "########################################" -ForegroundColor Green
            }
        }
    }

    function _openLog {
        param (
            [string[]] $logName
        )

        $logPath = @()

        $inaccessibleLogFolder = @()

        #region get log path
        foreach ($lName in $logName) {
            # most logs have static name but some are dynamic:
            # - CloudDP-<guid>.log
            # - SCClient_<domain>@<username>_1.log
            # - SCNotify_<domain>@<username>_1-<date_time>.log
            # - SleepAgent_<domain>@SYSTEM_0.log
            # - CCMClient-<date_time>.log
            # - CCMAgent-<date_time>.log
            # - CCMNotifications-<date_time>.log
            # - CCMPrefPane-<date_time>.log
            # - CMG-zzzxxxyyy-ProxyService_IN_0-CMGxxx.log

            Write-Verbose "Processing '$lName' log"

            if ($lName -eq 'CMRcViewer') {
                Write-Warning "Log 'CMRcViewer' is saved on the computer that runs the remote control viewer, in the %temp% folder. For sake of this function it is searched on computer defined in computerName parameter (a.k.a. $computerName)"
            }

            $logFolder = $logDetails | ? logName -Contains $lName | select -ExpandProperty logFolder
            if (!$logFolder) { throw "Undefined destination folder for log $lName. Define it inside this function in `$logDetails" }

            $wantedLog = $null

            # some logs are in multiple locations (therefore foreach)
            foreach ($lFolder in $logFolder) {
                if ($lFolder -in $inaccessibleLogFolder) {
                    Write-Verbose "Skipping inaccessible '$lFolder'"
                    continue
                }

                #region checks
                if (!$SCCMServer -and ($lFolder -in $serverLog, $serverDISMLog)) {
                    throw "You haven't specified SCCMServer parameter but log '$lName' is saved on SCCM server."
                }

                if (!$WSUSServer -and ($lFolder -in $WSUSLog)) {
                    throw "You haven't specified WSUSServer parameter but log '$lName' is saved on WSUS server."
                }

                if (!$serviceConnectionPointServer -and ($lFolder -in $serviceConnectionPointLog)) {
                    throw "You haven't specified serviceConnectionPointServer parameter but log '$lName' is saved on Service Connection Point server."
                }
                #endregion checks

                # get all possible log
                try {
                    # <log> OR <log>-<guid> OR <log>_<domain>@<username> OR <log>-<date_time> OR CMG-<tenantdata><log>
                    $regEscLog = [regex]::Escape($lName)
                    $availableLogs = Get-ChildItem $lFolder -Force -File -ErrorAction Stop | ? Name -Match "$regEscLog\.log?$|$regEscLog-[A-Z0-9-]+\.log?$|$regEscLog`_.+@.+\.log?$|$regEscLog-[0-9-]+\.log?$|CMG-.+$regEscLog" | Sort-Object LastWriteTime -Descending | Select-Object -ExpandProperty FullName
                } catch {
                    Write-Error "Unable to get logs from '$lFolder'. Error was: $_"
                    $inaccessibleLogFolder += $lFolder
                    continue
                }

                if ($availableLogs) {
                    #region add wanted log
                    # omit '.lo_' logs because they are archived logs
                    $wantedLog = $availableLogs | ? { $_ -match "\.log$" } | select -First 1

                    if ($wantedLog) {
                        Write-Verbose "`t- adding:`n'$wantedLog'"
                        $logPath += $wantedLog
                    }
                    #endregion add wanted log

                    #region add archived log(s)
                    if ($maxHistory -and $wantedLog) {
                        # $wantedLog is set means that I am searching in the right folder
                        $archivedLog = @($availableLogs | Select-Object -Skip 1 -First $maxHistory)

                        if ($archivedLog) {
                            Write-Verbose "`t- adding archive(s):`n$($archivedLog -join "`n")"
                            $logPath = @($logPath) + @($archivedLog) | Select-Object -Unique
                        } else {
                            Write-Verbose "`t- there are no archived versions"
                        }
                    }
                    #endregion add archived log(s)
                }
            }

            if (!$wantedLog) {
                Write-Warning "No '$lName' logs found in $(($logFolder | % {"'$_'"} ) -join ', ')"
            }
        }
        #endregion get log path

        #region open the log(s)
        if ($logPath) {
            if ($viewer -and $viewer -match "CMLogViewer\.exe$") {
                # open all logs in one CMLogViewer instance
                $quotedLog = ($logPath | % {
                        "`"$_`""
                    }) -join " "
                Start-Process $viewer -ArgumentList $quotedLog
            } elseif ($viewer -and $viewer -match "CMPowerLogViewer\.exe$") {
                # open all logs in one CMPowerLogViewer instance
                $quotedLog = ($logPath | % {
                        "`"$_`""
                    }) -join " "
                Start-Process $viewer -ArgumentList "--files $quotedLog"
            } else {
                # cmtrace (or notepad) don't support opening multiple logs in one instance, so open each log in separate viewer process
                foreach ($lPath in $logPath) {
                    if (!(Test-Path $lPath -ErrorAction SilentlyContinue)) {
                        continue
                    }

                    Write-Verbose "Opening $lPath"
                    if ($viewer -and $viewer -match "CMTrace\.exe$") {
                        # in case CMTrace viewer exists, use it
                        Start-Process $viewer -ArgumentList "`"$lPath`""
                    } else {
                        # use associated viewer
                        & $lPath
                    }
                }
            }
        } else {
            Write-Warning "There is no log to open"
        }
        #endregion open the log(s)
    }
    #endregion helper functions

    #region get log description from Microsoft documentation page
    $cachedLogDescription = "$env:TEMP\cachedLogDescription_8437973289.xml"
    $thresholdForGetNewData = 180
    $script:logDescription = $null

    if ((Test-Path $cachedLogDescription -ErrorAction SilentlyContinue) -and (Get-Item $cachedLogDescription).LastWriteTime -gt [datetime]::Now.AddDays(-$thresholdForGetNewData)) {
        # use cached version
        Write-Verbose "Use cached version of log information from $((Get-Item $cachedLogDescription).LastWriteTime)"
        $script:logDescription = Import-Clixml $cachedLogDescription
    } else {
        # get recent data and cache them
        try {
            _getAndCacheLogDescription
        } catch {
            Write-Warning $_
        }
    }
    #endregion get log description from Microsoft documentation page

    # hash where key is name of the area and value is hash with logs that should be opened and info that should be outputted
    # allowed keys in nested hash: log, writeHost, warningHost
    $areaDetails = @{
        "ApplicationInstallation"                    = @{
            log       = 'AppDiscovery', 'AppEnforce', 'AppIntentEval', 'Execmgr'
            writeHost = "More info at https://blogs.technet.microsoft.com/sudheesn/2011/01/31/troubleshooting-sccm-part-vi-software-distribution/"
        }

        "ApplicationDiscovery"                       = @{
            log = 'AppDiscovery'
        }

        "ApplicationDownload"                        = @{
            log       = 'DataTransferService'
            writeHost = "You can also try to run: Get-BitsTransfer -AllUsers | sort jobid | Format-List *"
        }

        "PXE"                                        = @{
            log = 'Distmgr', 'Smspxe', 'MP_ClientIDManager'
        }

        "ContentDistribution"                        = @{
            log = 'Distmgr'
        }

        "OSDeployment_clientPerspective"             = @{
            log = 'MP_ClientIDManager', 'Smsts', 'Execmgr'
        }

        "ClientInstallation"                         = @{
            log = 'Ccmsetup', 'Ccmsetup-ccmeval', 'CcmRepair', 'Client.msi', 'client.msi_uninstall'
        }

        "ClientPush"                                 = @{
            log = 'ccm'
        }

        "ApplicationMetering"                        = @{
            log = 'mtrmgr'
        }

        "Co-Management"                              = @{
            log       = 'CoManagementHandler', 'ComplRelayAgent'
            writeHost = "Check also Event Viewer: 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin' and 'Microsoft-Windows-AAD/Operational'"
        }

        "PolicyProcessing"                           = @{
            log = 'PolicyAgent', 'CcmMessaging'
        }

        "CMG"                                        = @{
            log          = 'CloudMgr', 'SMS_CLOUD_PROXYCONNECTOR', 'CMGService', 'CMGSetup', 'CMGContentService'
            writeWarning = "CMG* logs are stored on CMG machine and periodically downloaded to SCCM server. So there can be delay (approx. 10 minutes)."
        }

        "CMGDeployments"                             = @{
            log          = 'CloudMgr', 'CMGSetup'
            writeWarning = "CMG* logs are stored on CMG machine and periodically downloaded to SCCM server. So there can be delay (approx. 10 minutes)."
        }

        "CMGHealth"                                  = @{
            log          = 'CMGService', 'SMS_Cloud_ProxyConnector'
            writeWarning = "CMG* logs are stored on CMG machine and periodically downloaded to SCCM server. So there can be delay (approx. 10 minutes)."
        }

        "CMGClientTraffic"                           = @{
            log          = 'CMGHttpHandler', 'CMGService', 'SMS_Cloud_ProxyConnector'
            writeWarning = "CMG* logs are stored on CMG machine and periodically downloaded to SCCM server. So there can be delay (approx. 10 minutes)."
        }

        "Compliance"                                 = @{
            log = 'CIAgent', 'CITaskManager', 'DCMAgent', 'DCMReporting', 'DcmWmiProvider'
        }

        "Discovery"                                  = @{
            log = 'adsgdis', 'adsysdis', 'adusrdis', 'ADForestDisc', 'ddm', 'InventoryAgent', 'netdisc'
        }

        "Inventory"                                  = @{
            log = 'InventoryAgent'
        }

        "InventoryProcessing"                        = @{
            log = 'dataldr', 'invproc', 'sinvproc'
        }

        "WOL"                                        = @{
            log = 'Wolmgr', 'WolCmgr'
        }

        "NotificationServerInstall"                  = @{
            log = 'BgbSetup', 'bgbisapiMSI'
        }

        "NotificationServer"                         = @{
            log = 'bgbmgr', 'BGBServer', 'BgbHttpProxy'
        }

        "NotificationClient"                         = @{
            log = 'CcmNotificationAgent'
        }

        "BootImageUpdate"                            = @{
            log = 'dism'
        }

        "ApplicationManagement"                      = @{
            log = 'AppIntentEval', 'AppDiscovery', 'AppEnforce', 'AppGroupHandler', 'BusinessAppProcessWorker', 'Ccmsdkprovider', 'colleval', 'WsfbSyncWorker', 'NotiCtrl', 'PrestageContent', 'SettingsAgent', 'SMS_BUSINESS_APP_PROCESS_MANAGER', 'SMS_CLOUDCONNECTION', 'SMS_ImplicitUninstall', 'SMSdpmon', 'SoftwareCenterSystemTasks', 'TSDTHandler'
        }

        "PackagesAndPrograms"                        = @{
            log = 'colleval', 'execmgr'
        }

        "AssetIntelligence"                          = @{
            log = 'AssetAdvisor', 'aikbmgr', 'AIUpdateSvc', 'AIUSMSI', 'AIUSSetup', 'ManagedProvider', 'MVLSImport'
        }

        "BackupAndRecovery"                          = @{
            log = 'ConfigMgrSetup', 'Smsbkup', 'smssqlbkup', 'Smswriter'
        }

        "CertificateEnrollment"                      = @{
            log       = 'CertEnrollAgent', 'Crp', 'Crpctrl', 'Crpsetup', 'Crpmsi', 'NDESPlugin'
            writeHost = "You can also use the following log files:`nIIS log files for Network Device Enrollment Service: %SYSTEMDRIVE%\inetpub\logs\LogFiles\W3SVC1`nIIS log files for the certificate registration point: %SYSTEMDRIVE%\inetpub\logs\LogFiles\W3SVC1`nAnd mscep.log (This file is located in the folder for the NDES account profile, for example, in C:\Users\SCEPSvc)"
        }

        "ClientNotification"                         = @{
            log = 'bgbmgr', 'BGBServer', 'BgbSetup', 'bgbisapiMSI', 'BgbHttpProxy', 'CcmNotificationAgent'
        }

        "ComplianceSettingsAndCompanyResourceAccess" = @{
            log = 'CIAgent', 'CITaskManager', 'DCMAgent', 'DCMReporting', 'DcmWmiProvider'
        }

        "ConfigurationManagerConsole"                = @{
            log = 'ConfigMgrAdminUISetup', 'SmsAdminUI', 'Smsprov'
        }

        "ContentManagement"                          = @{
            log = 'CloudDP', 'CloudMgr', 'DataTransferService', 'PullDP', 'PrestageContent', 'PkgXferMgr', 'SMSdpmon', 'smsdpprov', 'smsdpusage'
        }

        "DesktopAnalytics"                           = @{
            log = 'M365ADeploymentPlanWorker', 'M365ADeviceHealthWorker', 'M365AHandler', 'M365AUploadWorker', 'SmsAdminUI'
        }

        "EndpointAnalytics"                          = @{
            log = 'UXAnalyticsUploadWorker', 'SensorWmiProvider', 'SensorEndpoint', 'SensorManagedProvider'
        }

        "EndpointProtection"                         = @{
            log = 'EndpointProtectionAgent', 'EPCtrlMgr', 'EPMgr', 'EPSetup'
        }

        "Extensions"                                 = @{
            log = 'AdminUI.ExtensionInstaller', 'FeatureExtensionInstaller', 'SmsAdminUI'
        }

        "Metering"                                   = @{
            log = 'mtrmgr', 'SWMTRReportGen', 'swmproc'
        }

        "Migration"                                  = @{
            log = 'migmctrl'
        }

        "MobileDevicesEnrollment"                    = @{
            log = 'DMPRP', 'dmpmsi', 'DMPSetup', 'enrollsrvMSI', 'enrollmentweb', 'enrollwebMSI', 'enrollmentservice', 'SMS_DM'
        }

        "ExchangeServerConnector"                    = @{
            log = 'easdisc'
        }

        "MobileDeviceLegacy"                         = @{
            log = 'DmCertEnroll', 'DMCertResp.htm', 'DmClientHealth', 'DmClientRegistration', 'DmClientSetup', 'DmClientXfer', 'DmCommonInstaller', 'DmInstaller', 'DmpDatastore', 'DmpDiscovery', 'DmpHardware', 'DmpIsapi', 'dmpmsi', 'DMPSetup', 'DmpSoftware', 'DmpStatus', 'DmSvc', 'FspIsapi'
        }

        "OSDeployment"                               = @{
            log = 'CAS', 'ccmsetup', 'CreateTSMedia', 'Dism', 'Distmgr', 'DriverCatalog', 'mcsisapi', 'mcsexec', 'mcsmgr', 'mcsprv', 'MCSSetup', 'MCSMSI', 'Mcsperf', 'MP_ClientIDManager', 'MP_DriverManager', 'OfflineServicingMgr', 'Setupact', 'Setupapi', 'Setuperr', 'smpisapi', 'Smpmgr', 'smpmsi', 'smpperf', 'smspxe', 'smssmpsetup', 'SMS_PhasedDeployment', 'Smsts', 'TSAgent', 'TaskSequenceProvider', 'loadstate', 'scanstate'
        }

        "PowerManagement"                            = @{
            log = 'pwrmgmt'
        }

        "RemoteControl"                              = @{
            log = 'CMRcViewer'
        }

        "Reporting"                                  = @{
            log = 'srsrp', 'srsrpMSI', 'srsrpsetup'
        }

        "Role-basedAdministration"                   = @{
            log = 'hman', 'SMSProv'
        }

        "SoftwareMetering"                           = @{
            log = 'mtrmgr'
        }

        "SoftwareUpdates"                            = @{
            log = 'AlternateHandler', 'ccmperf', 'DeltaDownload', 'PatchDownloader', 'PolicyEvaluator', 'RebootCoordinator', 'ScanAgent', 'SdmAgent', 'ServiceWindowManager', 'SMS_ISVUPDATES_SYNCAGENT', 'SMS_OrchestrationGroup', 'SmsWusHandler', 'StateMessage', 'SUPSetup', 'UpdatesDeployment', 'UpdatesHandler', 'UpdatesStore', 'WCM', 'WSUSCtrl', 'wsyncmgr', 'WUAHandler'
        }

        "WindowsServicing"                           = @{
            log = 'CBS', 'DISM', 'setupact'
        }

        "WindowsUpdateAgent"                         = @{
            log = 'WindowsUpdate'
        }

        "WSUSServer"                                 = @{
            log = 'Change', 'SoftwareDistribution'
        }
    }
    #endregion prepare

    #region open corresponding logs etc
    if ($area) {
        $result = $areaDetails.GetEnumerator() | ? Key -EQ $area | select -ExpandProperty Value

        if (!$result) { throw "Undefined area '$area'" }

        $logName = $result.log | Sort-Object
    } else {
        # user have used logName parameter
    }

    Write-Warning "Opening log(s): $($logName -join ', ')"

    # output logs description
    _getLogDescription $logName

    if ($result.writeHost) { Write-Host ("`n" + $result.writeHost + "`n") }
    if ($result.writeWarning) { Write-Warning $result.writeWarning }

    # open logs
    _openLog $logName
    #endregion open corresponding logs etc
}

function Invoke-CMAdminServiceQuery {
    <#
    .SYNOPSIS
    Function for retrieving information from SCCM Admin Service REST API.
    Will connect to API and return results according to given query.
    Supports local connection and also internet through CMG.
 
    .DESCRIPTION
    Function for retrieving information from SCCM Admin Service REST API.
    Will connect to API and return results according to given query.
    Supports local connection and also internet through CMG.
    Use credentials with READ rights on queried source at least.
    For best performance defined filter and select parameters.
 
    .PARAMETER ServerFQDN
    For intranet clients
    The fully qualified domain name of the server hosting the AdminService
 
    .PARAMETER Source
    For specifying what information are we looking for. You can use TAB completion!
    Accept string representing the source in format <source>/<wmiclass>.
    SCCM Admin Service offers two base Source:
     - wmi = for WMI classes (use it like wmi/<className>)
        - examples:
            - wmi/ = list all available classes
            - wmi/SMS_R_System = get all systems (i.e. content of SMS_R_System WMI class)
            - wmi/SMS_R_User = get all users
     - v1.0 = for WMI classes, that were migrated to this new Source
        - example v1.0/ = list all available classes
        - example v1.0/Application = get all applications
 
    .PARAMETER Filter
    For filtering the returned results.
    Accept string representing the filter statement.
    Makes query significantly faster!
 
    Examples:
    - "name eq 'ni-20-ntb'"
    - "startswith(Name,'Drivers -')"
 
    Usable operators:
    any, all, cast, ceiling, concat, contains, day, endswith, filter, floor, fractionalseconds, hour, indexof, isof, length, minute, month, round, second, startswith, substring, tolower, toupper, trim, year, date, time
 
    https://docs.microsoft.com/en-us/graph/query-parameters
 
    .PARAMETER Select
    For filtering returned properties.
    Accept list of properties you want to return.
    Makes query significantly faster!
 
    Examples:
    - "MACAddresses", "Name"
 
    .PARAMETER ExternalUrl
    For internet clients
    ExternalUrl of the AdminService you wish to connect to. You can find the ExternalUrl by directly querying your CM database.
    Query: SELECT ProxyServerName,ExternalUrl FROM [dbo].[vProxy_Routings] WHERE [dbo].[vProxy_Routings].ExternalEndpointName = 'AdminService'
    It should look like this: HTTPS://<YOURCMG>.<FQDN>/CCM_Proxy_ServerAuth/<RANDOM_NUMBER>/AdminService
 
    .PARAMETER TenantId
    For internet clients
    Azure AD Tenant ID that is used for your CMG
 
    .PARAMETER ClientId
    For internet clients
    Client ID of the application registration created to interact with the AdminService
 
    .PARAMETER ApplicationIdUri
    For internet clients
    Application ID URI of the Configuration manager Server app created when creating your CMG.
    The default value of 'https://ConfigMgrService' should be good for most people.
 
    .PARAMETER BypassCertCheck
    Enabling this option will allow PowerShell to accept any certificate when querying the AdminService.
    If you do not enable this option, you need to make sure the certificate used by the AdminService is trusted by the device.
 
    .EXAMPLE
    Invoke-CMAdminServiceQuery -Source wmi/
 
    Use TAB for getting all available wmi sources.
 
    .EXAMPLE
    Invoke-CMAdminServiceQuery -Source v1.0/
 
    Use TAB for getting all available v1.0 sources.
 
    .EXAMPLE
    Invoke-CMAdminServiceQuery -Source "wmi/SMS_R_SYSTEM" -Filter "name eq 'ni-20-ntb'" -Select MACAddresses
 
    .EXAMPLE
    Invoke-CMAdminServiceQuery -Source "wmi/SMS_R_SYSTEM" -Filter "startswith(Name,'AE-')" -Select Name, MACAddresses
 
    .NOTES
    !!!Credits goes to author of https://github.com/CharlesNRU/mdm-adminservice/blob/master/Invoke-GetPackageIDFromAdminService.ps1 (I just generalize it and made some improvements)
    Lot of useful information https://www.asquaredozen.com/2019/02/12/the-system-center-configuration-manager-adminservice-guide
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory = $false, HelpMessage = "Set the FQDN of the server hosting the ConfigMgr AdminService.", ParameterSetName = "Intranet")]
        [ValidateNotNullOrEmpty()]
        [string] $ServerFQDN = $_SCCMServer
        ,
        [Parameter(Mandatory = $true)]
        [ValidateScript( {
                If ($_ -match "(^wmi/)|(^v1.0/)") {
                    $true
                } else {
                    Throw "$_ is not a valid source (for example: wmi/SMS_Package or v1.0/whatever"
                }
            })]
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
                $source = ($WordToComplete -split "/")[0]
                $class = ($WordToComplete -split "/")[1]
                Invoke-CMAdminServiceQuery -Source "$source/" | ? { $_.url -like "*$class*" } | select -exp url | % { "$source/$_" }
            })]
        [string] $Source
        ,
        [string] $Filter
        ,
        [string[]] $Select
        ,
        [parameter(Mandatory = $true, HelpMessage = "Set the CMG ExternalUrl for the AdminService.", ParameterSetName = "Internet")]
        [ValidateNotNullOrEmpty()]
        [string] $ExternalUrl
        ,
        [parameter(Mandatory = $true, HelpMessage = "Set your TenantID.", ParameterSetName = "Internet")]
        [ValidateNotNullOrEmpty()]
        [string] $TenantID
        ,
        [parameter(Mandatory = $true, HelpMessage = "Set the ClientID of app registration to interact with the AdminService.", ParameterSetName = "Internet")]
        [ValidateNotNullOrEmpty()]
        [string] $ClientID
        ,
        [parameter(Mandatory = $false, HelpMessage = "Specify URI here if using non-default Application ID URI for the configuration manager server app.", ParameterSetName = "Internet")]
        [ValidateNotNullOrEmpty()]
        [string] $ApplicationIdUri = 'https://ConfigMgrService'
        ,
        [parameter(Mandatory = $false, HelpMessage = "Specify the credentials that will be used to query the AdminService.", ParameterSetName = "Intranet")]
        [parameter(Mandatory = $true, HelpMessage = "Specify the credentials that will be used to query the AdminService.", ParameterSetName = "Internet")]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential] $Credential
        ,
        [parameter(Mandatory = $false, HelpMessage = "If set to True, PowerShell will bypass SSL certificate checks when contacting the AdminService.", ParameterSetName = "Intranet")]
        [parameter(Mandatory = $false, HelpMessage = "If set to True, PowerShell will bypass SSL certificate checks when contacting the AdminService.", ParameterSetName = "Internet")]
        [bool]$BypassCertCheck = $false
    )

    Begin {
        #region functions
        function Get-AdminServiceUri {
            switch ($PSCmdlet.ParameterSetName) {
                "Intranet" {
                    if (!$ServerFQDN) { throw "ServerFQDN isn't defined" }
                    Return "https://$($ServerFQDN)/AdminService"
                }
                "Internet" {
                    if (!$ExternalUrl) { throw "ExternalUrl isn't defined" }
                    Return $ExternalUrl
                }
            }
        }

        function Import-MSALPSModule {
            Write-Verbose "Checking if MSAL.PS module is available on the device."
            $MSALModule = Get-Module -ListAvailable MSAL.PS
            If ($MSALModule) {
                Write-Verbose "Module is already available."
            } Else {
                #Setting PowerShell to use TLS 1.2 for PowerShell Gallery
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

                Write-Verbose "MSAL.PS is not installed, checking for prerequisites before installing module."

                Write-Verbose "Checking for NuGet package provider... "
                If (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
                    Write-Verbose "NuGet package provider is not installed, installing NuGet..."
                    $NuGetVersion = Install-PackageProvider -Name NuGet -Force -ErrorAction Stop | Select-Object -ExpandProperty Version
                    Write-Verbose "NuGet package provider version $($NuGetVersion) installed."
                }

                Write-Verbose "Checking for PowerShellGet module version 2 or higher "
                $PowerShellGetLatestVersion = Get-Module -ListAvailable -Name PowerShellGet | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version
                If ((-not $PowerShellGetLatestVersion)) {
                    Write-Verbose "Could not find any version of PowerShellGet installed."
                }
                If (($PowerShellGetLatestVersion.Major -lt 2)) {
                    Write-Verbose "Current PowerShellGet version is $($PowerShellGetLatestVersion) and needs to be updated."
                }
                If ((-not $PowerShellGetLatestVersion) -or ($PowerShellGetLatestVersion.Major -lt 2)) {
                    Write-Verbose "Installing latest version of PowerShellGet..."
                    Install-Module -Name PowerShellGet -AllowClobber -Force
                    $InstalledVersion = Get-Module -ListAvailable -Name PowerShellGet | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version
                    Write-Verbose "PowerShellGet module version $($InstalledVersion) installed."
                }

                Write-Verbose "Installing MSAL.PS module..."
                If ((-not $PowerShellGetLatestVersion) -or ($PowerShellGetLatestVersion.Major -lt 2)) {
                    Write-Verbose "Starting another powershell process to install the module..."
                    $result = Start-Process -FilePath powershell.exe -ArgumentList "Install-Module MSAL.PS -AcceptLicense -Force" -PassThru -Wait -NoNewWindow
                    If ($result.ExitCode -ne 0) {
                        Write-Verbose "Failed to install MSAL.PS module"
                        Throw "Failed to install MSAL.PS module"
                    }
                } Else {
                    Install-Module MSAL.PS -AcceptLicense -Force
                }
            }
            Write-Verbose "Importing MSAL.PS module..."
            Import-Module MSAL.PS -Force
            Write-Verbose "MSAL.PS module successfully imported."
        }
        #endregion functions
    }

    Process {
        Try {
            #region connect Admin Service
            Write-Verbose "Processing credentials..."
            switch ($PSCmdlet.ParameterSetName) {
                "Intranet" {
                    If ($Credential) {
                        If ($Credential.GetNetworkCredential().password) {
                            Write-Verbose "Using provided credentials to query the AdminService."
                            $InvokeRestMethodCredential = @{
                                "Credential" = ($Credential)
                            }
                        } Else {
                            throw "Username provided without a password, please specify a password."
                        }
                    } Else {
                        Write-Verbose "No credentials provided, using current user credentials to query the AdminService."
                        $InvokeRestMethodCredential = @{
                            "UseDefaultCredentials" = $True
                        }
                    }

                }
                "Internet" {
                    Import-MSALPSModule

                    Write-Verbose "Getting access token to query the AdminService via CMG."
                    $Token = Get-MsalToken -TenantId $TenantID -ClientId $ClientID -UserCredential $Credential -Scopes ([String]::Concat($($ApplicationIdUri), '/user_impersonation')) -ErrorAction Stop
                    Write-Verbose "Successfully retrieved access token."
                }
            }

            If ($BypassCertCheck) {
                Write-Verbose "Bypassing certificate checks to query the AdminService."
                #Source: https://til.intrepidintegration.com/powershell/ssl-cert-bypass.html
                Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@

                [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3, [Net.SecurityProtocolType]::Tls, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls12
            }
            #endregion connect Admin Service

            #region make&execute query
            $URI = (Get-AdminServiceUri) + "/" + $Source

            $Body = @{}

            if ($Filter) {
                $Body."`$filter" = $Filter
            }
            if ($Select) {
                $Body."`$select" = ($Select -join ",")
            }

            switch ($PSCmdlet.ParameterSetName) {
                'Intranet' {
                    Invoke-RestMethod -Method Get -Uri $URI -Body $Body @InvokeRestMethodCredential | Select-Object -ExpandProperty value
                }
                'Internet' {
                    $authHeader = @{
                        'Content-Type'  = 'application/json'
                        'Authorization' = "Bearer " + $token.AccessToken
                        'ExpiresOn'     = $token.ExpiresOn
                    }
                    $Packages = Invoke-RestMethod -Method Get -Uri $URI -Headers $authHeader -Body $Body | Select-Object -ExpandProperty value
                }
            }
            #endregion make&execute query
        } Catch {
            throw "Error: $($_.Exception.HResult)): $($_.Exception.Message)`n$($_.InvocationInfo.PositionMessage)"
        }
    }
}

function Invoke-CMAppInstall {
    <#
        .SYNOPSIS
            Spusti instalaci nenainstalovanych aplikaci (viditelnych v Software Center). Ale pouze pokud nevyzaduji restart mimo service window.
            Vyzaduje pripojeni na remote WMI.
 
        .DESCRIPTION
            Spusti instalaci nenainstalovanych aplikaci (viditelnych v Software Center). Ale pouze pokud nevyzaduji restart mimo service window.
            Vyzaduje pripojeni na remote WMI.
 
        .PARAMETER ComputerName
            Jmeno stroje/u kde se ma provest vynuceni insstalace.
 
        .PARAMETER appName
            Jmeno aplikace, jejiz instalace se ma vynutit.
            Staci cast nazvu.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "zadej jmeno stroje/ů")]
        [Alias("c", "CN", "__Server", "IPAddress", "Server", "Computer", "Name", "SamAccountName")]
        [ValidateNotNullOrEmpty()]
        [string[]] $computerName = $env:computerName
        ,
        [Parameter(Mandatory = $false, Position = 1)]
        [string] $appName
    )

    begin {
        $appName = "*" + $appName + "*"
    }

    process {
        Write-Verbose "Will query '$($Clients.Count)' clients"
        foreach ($Computer in $Computername) {
            try {
                if (!(Test-Connection -ComputerName $Computer -Quiet -Count 1)) {
                    throw "$Computer is offline"
                } else {
                    $Params = @{
                        'Namespace' = 'root\ccm\clientsdk'
                        'Class'     = 'CCM_Application'
                    }

                    if ($Computer -notin "localhost", $env:computerName) {
                        $params.computerName = $Computer
                    }

                    $ApplicationClass = [WmiClass]"\\$Computer\root\ccm\clientSDK:CCM_Application"
                    # EvaluationState 1 je Required
                    # EvaluationState 3 je Available
                    Get-CimInstance @Params | Where-Object { $_.FullName -like $appName -and $_.InstallState -notlike "installed" -and $_.ApplicabilityState -eq "Applicable" -and $_.EvaluationState -eq 1 -and $_.RebootOutsideServiceWindow -eq $false } | % {
                        $Application = $_
                        $ApplicationID = $Application.Id
                        $ApplicationRevision = $Application.Revision
                        $ApplicationIsMachineTarget = $Application.ismachinetarget
                        $EnforcePreference = "Immediate"
                        $Priority = "high"
                        $IsRebootIfNeeded = $false

                        Write-Output "Na $computer instaluji $($application.fullname)"
                        $null = $ApplicationClass.Install($ApplicationID, $ApplicationRevision, $ApplicationIsMachineTarget, 0, $Priority, $IsRebootIfNeeded)
                        # spravne poradi parametru ziskam pomoci $ApplicationClass.GetMethodParameters("install") | select -first 1 | select -exp properties
                        #Invoke-CimMethod -ComputerName titan02 -Class $Params.Class -Namespace $Params.Namespace -Name install -ArgumentList 0,$ApplicationID,$ApplicationIsMachineTarget,$IsRebootIfNeeded,$Priority,$ApplicationRevision
                    }
                }
            } catch {
                Write-Warning $_.Exception.Message
            }
        }
    }
}

function Invoke-CMClientReinstall {
    [cmdletbinding()]
    param (
        [string] $computerName = $env:COMPUTERNAME
    )

    $ErrorActionPreference = "Stop"

    $oSCCM = [wmiclass] "\\$computerName\root\ccm:sms_client"
    $oSCCM.RepairClient()

    "Repair on $computerName has started"

    Write-Warning "Installation can take from 5 to 30 minutes! Check current status using: Get-CMLog -computerName $computerName -problem CMClientInstallation"
}

function Invoke-CMComplianceEvaluation {
    <#
    .SYNOPSIS
    Function triggers evaluation of available SCCM compliance baselines.
 
    .DESCRIPTION
    Function triggers evaluation of available SCCM compliance baselines.
    It supports evaluation of device and user compliance policies! Users part thanks to Invoke-AsCurrentUser.
    Disadvantage is, that function returns string as output, not object, but only in case, you run it against remote computer (locally is used classic Invoke-Command).
 
    .PARAMETER computerName
    Default is localhost.
 
    .PARAMETER baselineName
    Optional parameter for filtering baselines to evaluate.
 
    .EXAMPLE
    Invoke-CMComplianceEvaluation
 
    Trigger evaluation of all compliance baselines on localhost targeted to device and user, that run this function.
 
    .EXAMPLE
    Invoke-CMComplianceEvaluation -computerName ae-01-pc -baselineName "KTC_compliance_policy"
 
    Trigger evaluation of just KTC_compliance_policy compliance baseline on ae-01-pc. But only in case, such baseline is targeted to device, not user.
 
    .NOTES
    Modified from https://social.technet.microsoft.com/Forums/en-US/76afbba5-065e-4809-9720-024ea05d6cee/trigger-baseline-evaluation?forum=configmanagersdk
    #>


    [CmdletBinding()]
    param (
        [parameter(ValueFromPipeline = $true)]
        [string] $computerName
        ,
        [string[]] $baselineName
    )

    #region prepare param for Invoke-AsLoggedUser
    $param = @{ReturnTranscript = $true }

    if ($baselineName) {
        $param.argument = @{baselineName = $baselineName }
    }

    if ($computerName -and $computerName -notmatch "localhost|$env:COMPUTERNAME") {
        $param.computerName = $computerName
    }
    #endregion prepare param for Invoke-AsLoggedUser

    $scriptBlockText = @'
#Start-Transcript (Join-Path $env:TEMP ((Split-Path $PSCommandPath -Leaf) + ".log"))
 
$Baselines = Get-CimInstance -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
ForEach ($Baseline in $Baselines) {
    $bsDisplayName = $Baseline.DisplayName
    if ($baselineName -and $bsDisplayName -notin $baselineName) {
        Write-Verbose "Skipping $bsDisplayName baseline"
        continue
    }
 
    $name = $Baseline.Name
    $IsMachineTarget = $Baseline.IsMachineTarget
    $IsEnforced = $Baseline.IsEnforced
    $PolicyType = $Baseline.PolicyType
    $version = $Baseline.Version
 
    $MC = [WmiClass]"\\localhost\root\ccm\dcm:SMS_DesiredConfiguration"
 
    $Method = "TriggerEvaluation"
    $InParams = $MC.psbase.GetMethodParameters($Method)
    $InParams.IsEnforced = $IsEnforced
    $InParams.IsMachineTarget = $IsMachineTarget
    $InParams.Name = $name
    $InParams.Version = $version
    $InParams.PolicyType = $PolicyType
 
    switch ($Baseline.LastComplianceStatus) {
        0 {$bsStatus = "Noncompliant"}
        1 {$bsStatus = "Compliant"}
        default {$bsStatus = "Noncompliant"}
    }
    "Evaluating: '$bsDisplayName' Last status: $bsStatus Last evaluated: $($Baseline.LastEvalTime)"
 
    $result = $MC.InvokeMethod($Method, $InParams, $null)
 
    if ($result.ReturnValue -eq 0) {
        Write-Verbose "OK"
    } else {
        Write-Error "There was an error.`n$result"
    }
}
'@
 # end of scriptBlock text

    $scriptBlock = [Scriptblock]::Create($scriptBlockText)

    if ($param.computerName) {
        Invoke-AsLoggedUser -ScriptBlock $scriptBlock @param
    } else {
        Invoke-Command -ScriptBlock $scriptBlock
    }
}

function New-CMAppDeployment {
    <#
        .SYNOPSIS
            Fce pro nasazeni aplikace/i na vybranou kolekci/e.
 
        .DESCRIPTION
            Fce pro nasazeni aplikace/i na vybranou kolekci/e.
            Po spusteni funkce bez parametru se zobrazi okno pro vyber baliku a nasledne kolekce, na kterou se ma nainstalovat.
            Baliky se nasazuji jako required, bez zobrazeni notifikace v liste, vcetne zobrazeni alertu v SCCM konzoli pri nejake neuspesne instalaci.
 
            Navic dojde k dotazu, zdali je aplikace licencovana. Pokud bude odpoved kladna, tak se u ni nastavi priznak,
            ze vyzaduje pred zapocetim instalace schvaleni SCCM spravcem.
 
        .PARAMETER AppName
            Nazev aplikace.
            Pokud se nezada, zobrazi se GUI se seznamem dostupnych aplikaci.
 
        .PARAMETER CollectionName
            Nazev kolekce, na kterou budeme instalovat.
            Pokud se nezada, zobrazi se GUI se seznamem dostupnych kolekci.
 
        .PARAMETER Purpose
            Type of deployment.
            Required or Available.
 
            Default is Required.
 
        .PARAMETER DeployAction
            Type of deployment.
            Install or Uninstall.
 
            Default is Install.
 
        .PARAMETER SiteCode
            Nepovinny parametr udavajici kod SCCM site.
 
        .PARAMETER SccmServer
            Nepovinny parametr udavajici jmeno SCCM serveru.
 
        .PARAMETER DPGroupName
            Name of DP group.
 
            Default is "DP Group"
 
        .PARAMETER force_install
            Switch rikajici jestli se ma provest na klientech update SCCM politik = uspisit nainstalovani aplikace.
            Vyzaduje fci Update-CMClientPolicy.
 
        .EXAMPLE
            New-CMAppDeployment
 
            Shows GUI for selecting application and collection and deploy it as required.
 
        .EXAMPLE
            New-CMAppDeployment -Purpose Available
 
            Shows GUI for selecting application and collection and deploy it as Available.
 
        .NOTES
            Fce pouziva fci connect-sccm pro pripojeni do SCCM, ale funguje i bez ni.
            Pouziva i Add-CMDeploymentTypeGlobalCondition, ktera vyzaduje, aby byla lokalne nainstalovana SCCM konzole!
    #>


    [CmdletBinding()]
    [Alias("Invoke-CMAppDeployment")]
    param(
        $AppName
        ,
        [array] $CollectionName
        ,
        [ValidateSet('Required', 'Available')]
        [string] $Purpose = "Required"
        ,
        [ValidateSet('Install', 'Uninstall')]
        [string] $DeployAction = "Install"
        ,
        $siteCode = $_SCCMSiteCode
        ,
        $sccmServer = $_SCCMServer
        ,
        $DPGroupName = "DP group"
        ,
        [switch] $force_install
    )

    $ConnectParameters = @{
        sccmServer  = $sccmServer
        commandName = "New-CMApplicationDeployment", "Get-CMCollection", "Start-CMContentDistribution", "Set-CMApplicationDeployment", "Get-CMCategory", "New-CMCategory", "Set-CMApplication", "Get-CMDeployment", "Get-CMApplication", "Get-CMDeploymentType"
        ErrorAction = "stop"
    }
    if ($VerbosePreference -eq 'continue') {
        $ConnectParameters.Add("verbose", $true)
    }

    Write-Output "Connecting to $sccmServer"
    Connect-SCCM @ConnectParameters

    if (!$AppName) {
        $AppName = Get-CMApplicationOGV -title "Choose application(s) to deploy"
    }
    if (!$AppName) { throw "No application was chosen!" }

    if (!$CollectionName) {
        $CollectionName = Get-CMCollectionOGV -title "Choose target collection(s)"
    }
    if (!$CollectionName) { throw "No collection was chosen!" }

    if ($DeployAction -eq "Uninstall" -and $Purpose -ne "Required") {
        Write-Warning "Purpose was set to Required, which is mandatory for uninstall action"
        $Purpose = "Required"
    }

    #
    # distribuce na DP pokud tam uz neni
    #
    foreach ($App in $AppName) {
        $AppID = Get-CimInstance -ComputerName $sccmServer -Namespace root\SMS\Site_$siteCode -Class SMS_PackageBaseclass -Filter "Name='$App'" | select -exp PackageID
        $distributed = Get-CimInstance -ComputerName $sccmServer -Namespace root\SMS\Site_$siteCode -Class SMS_DistributionStatus | where { $_.packageid -eq $AppID }
        if (!$distributed) {
            Write-Verbose "Application $App isn't on any DP, distributing"
            Start-CMContentDistribution -ApplicationName $App -DistributionPointGroupName $DPGroupName
        }
    }

    #
    # nasazeni na vybrane kolekce
    #
    foreach ($collection in $CollectionName) {
        # zjistim jestli je tato kolekce typu user collection
        $isUserCollection = Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query "SELECT * FROM SMS_Collection where name=`"$collection`" and collectiontype = 1"

        try {
            foreach ($App in $AppName) {
                $deployed = Get-CMDeployment -SoftwareName $App -CollectionName $collection | ? {
                    if (($DeployAction -eq "Install" -and $_.DesiredConfigType -eq 1) -or ($DeployAction -eq "Uninstall" -and $_.DesiredConfigType -eq 2)) { $true } else { $false } }
                if ($deployed) {
                    Write-Warning "Application $App is already deployed to $collection collection. Skipping"
                    continue
                }

                if (!$isUserCollection) {
                    [System.Collections.ArrayList] $appCategory = @(Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query "SELECT LocalizedCategoryInstanceNames FROM SMS_Application WHERE LocalizedDisplayName = `'$App`' AND IsLatest = 1" | select -exp LocalizedCategoryInstanceNames)
                }

                Write-Output "Deploy: $App to: $collection as: $Purpose"

                #
                # NASAZENI DEPLOYMENTU
                #

                # vytvorim hash s parametry, ktere se pouziji pro nasazeni deploymentu
                $params = @{
                    Name                  = $App
                    CollectionName        = $collection
                    DeployAction          = $DeployAction
                    DeployPurpose         = $Purpose
                    UserNotification      = 'DisplaySoftwareCenterOnly' #'DisplayAll'
                    TimeBaseOn            = 'LocalTime'
                    OverrideServiceWindow = $true
                    PostponeDateTime      = (Get-Date).AddDays(+7)
                    FailParameterValue    = 0
                    SuccessParameterValue = 100
                    ErrorAction           = 'stop'
                }

                # nasazuji na uzivatelskou kolekci
                if ($isUserCollection) {
                    # od kdy bude aplikace dostupna
                    $params.AvailableDateTime = Get-Date
                    # a tyto parametry jsou u uzivatelu zbytecne
                    # $params.Remove('SuccessParameterValue')
                    # $params.Remove('PostponeDateTime')
                    # $params.Remove('OverrideServiceWindow')

                    # nazev kategorie urcujici, ze jde o placeny SW
                    $licensedCategory = 'Licensed SW'
                    # aktualne nastaven SW kategorie
                    [System.Collections.ArrayList] $appCategory = @(Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query "SELECT LocalizedCategoryInstanceNames FROM SMS_Application WHERE LocalizedDisplayName = `'$App`' AND IsLatest = 1" | select -exp LocalizedCategoryInstanceNames)

                    # zjistim jestli jde o placeny SW
                    $licensedApp = ''
                    if ($appCategory -contains $licensedCategory) {
                        $licensedApp = 'Y' #Get-CimInstance -computername $sccmServer -Namespace "root\sms\site_$siteCode" -query "SELECT LocalizedDisplayName FROM SMS_Application WHERE LocalizedDisplayName = `'$App`' AND LocalizedCategoryInstanceNames = `'$licensedCategory`'"
                    }

                    $usedToBeFreeApp = 0
                    # aktualne neni oznacen jako placeny
                    if (!$licensedApp) {
                        # poznacim si, ze nemel nastavenou kategorii (ze je placeny)
                        ++$usedToBeFreeApp
                        # zjistim, jestli ma byt oznacen jako placeny
                        while ($licensedApp -notin ("Y", "N")) {
                            $licensedApp = Read-Host "Is admin approval necessary to deploy this app? Y|N (Default is N)"
                            if (!$licensedApp) { $licensedApp = 'N' }
                        }
                    }

                    # SW je placeny/Licencovany
                    if ($licensedApp -eq 'Y') {
                        <# instalace neslo spustit i kdyz dany uzuivatel byl na svem primary device...
                        # zjistim jestli jiz obsahuje omezeni na instalaci pouze na Primary Device uzivatele
                        $jenNaPrimaryDevice = Get-CMApplication $App | select sdmpackagexml | where {$_.sdmpackagexml -like "*Primarydevice*"}
                        # omezeni instalace neni nastaveno
                        if (!$jenNaPrimaryDevice) {
                            # omezim moznost instalace pouze na primary device uzivatele
                            Write-Verbose "Nastavuji requirement, aby sla instalovat pouze na Primary Device daneho uzivatele (nemohl ji instalovat kdekoli)"
                            $pouzivanyDeployment = Get-CMDeploymentType -ApplicationName $App | where {$_.PriorityInLatestApp -eq 1} | select -exp localizeddisplayname
                            try {
                                Add-CMDeploymentTypeGlobalCondition -ApplicationName $App -DeploymentTypeName $pouzivanyDeployment -sdkserver $sccmServer -sitecode $siteCode -GlobalCondition "PrimaryDevice" -Operator "IsEquals" -Value "True" -ea stop
                            } catch {
                                throw "Pri nastavovani podminky, aby sla aplikace instalovat pouze na Primary Device uzivatele se vyskytl problem:`n$_"
                            }
                        } else {
                            Write-Verbose "Uz ma nastaveno omezeni instalace pouze na Primary Device uzivatele"
                        }
                        #>


                        # nastavim nutnost schvaleni instalace adminem
                        Write-Verbose "Set approval for this app"
                        $params.add('approvalRequired', $true)

                        # nastavim kategorii 'Licencovany SW'
                        if ($usedToBeFreeApp) {
                            Write-Verbose "Set SW category to mark $App as paid/licensed"
                            [System.Collections.ArrayList] $appCategory = @(Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query "SELECT LocalizedCategoryInstanceNames FROM SMS_Application WHERE LocalizedDisplayName = `'$App`' AND IsLatest = 1" | select -exp LocalizedCategoryInstanceNames)

                            $appCategory.Add($licensedCategory) | Out-Null

                            Set-CMApplication -ApplicationName $App -AppCategories $appCategory
                            if (!$?) {
                                # try catch u tohoto cmdletu nefungoval
                                throw "When setting SW category there was an error. Make sure, that noone has this app opened in SCCM console."
                            }
                        } else {
                            Write-Output "Application $App was set as licensed, users will need admin approval before installation occurs."
                        }
                    } # konec licencovana app
                } # konec isUserCollection

                #
                # vytvorim novy deployment aplikace na vybranou kolekci
                #
                New-CMApplicationDeployment @params | Out-Null

                # umozneni instalace SW mimo maintenance windows. Skrze -OverrideServiceWindow nefunguje.
                Write-Verbose "allow installation outside the maintenance window"
                $apps = Get-CimInstance -Namespace "root\sms\site_$siteCode" -ComputerName $sccmServer -Query "SELECT * FROM SMS_ApplicationAssignment WHERE CollectionName = `'$collection`' and ApplicationName = `'$App`'"
                $apps.OverrideServiceWindows = $true
                $null = $apps.put()
            } # konec foreach cyklu resiciho instalace jednotlivych aplikaci
        } catch {
            throw $_
        }
    } # konec foreach cyklu resiciho jednotlive kolekce

    if (!$error) {
        Write-Output "OK"

        if ($force_install) {
            if (gcm Update-CMClientPolicy -ErrorAction silentlycontinue) {
                Write-Output "`nIn about 2 minutes, update of SCCM policy on computer in $($CollectionName -join ', ') begins"
                # zpozdeni je kvuli tomu, ze i na SCCM chvili trva, nez se zmeny v deploymentu projevi, tak aby klienti politiky kontrolovaly az budou updatovave
                Start-Job { sleep 120; Update-CMClientPolicy -CollectionName $CollectionName } | Out-Null
            } else {
                Write-Error "Function Update-CMClientPolicy ins't available, -force switch won't be used"
            }
        }
    }
}

function New-CMAppPhasedDeploment {
    <#
    .SYNOPSIS
    Function for creation of phased application deployment.
 
    .DESCRIPTION
    Function for creation of phased application deployment.
 
    .PARAMETER AppName
    Name of the deployed application.
 
    .PARAMETER phs1Collection
    Name of the test collection (phase 1 collection).
 
    .PARAMETER phs2Collection
    Name of the target collection (phase 2 collection).
 
    .PARAMETER DPGroupName
    Name of the distribution group.
    This will be used, if application is not distributed yet.
 
    .PARAMETER siteCode
    Name of the SCCM site.
 
    .PARAMETER sccmServer
    Name of the SCCM server.
 
    .EXAMPLE
    New-CMAppPhasedDeploment
 
    GUI for selecting application, phase 1 collection and phase 2 collection will let you choose, what you want to do.
    #>


    [CmdletBinding()]
    [Alias("Invoke-CMAppPhasedDeployment")]
    param(
        $AppName
        ,
        [string] $phs1Collection
        ,
        [string] $phs2Collection
        ,
        $DPGroupName = "DP group"
        ,
        $siteCode = $_SCCMSiteCode
        ,
        $sccmServer = $_SCCMServer
    )

    #region connect to SCCM server
    $ConnectParameters = @{
        sccmServer  = $sccmServer
        commandName = "New-CMApplicationAutoPhasedDeployment", "Start-CMContentDistribution"
        ErrorAction = "stop"
    }
    if ($VerbosePreference -eq 'continue') {
        $ConnectParameters.Add("verbose", $true)
    }

    Write-Output "Connecting to $sccmServer"
    Connect-SCCM @ConnectParameters
    #endregion connect to SCCM server

    #region get missing values
    if (!$AppName) {
        $AppName = Get-CMApplicationOGV -title "Choose application(s) to deploy"
    }
    if (!$AppName) { throw "No application was chosen!" }

    if (!$phs1Collection) {
        $phs1Collection = Get-CMCollectionOGV -title "Choose phase one collection"
    }
    if (!$phs1Collection) { throw "No collection was chosen!" }

    if (!$phs2Collection) {
        $phs2Collection = Get-CMCollectionOGV -title "Choose target collection"
    }
    if (!$phs2Collection) { throw "No collection was chosen!" }
    #endregion get missing values

    foreach ($App in $AppName) {
        # distribute app to DP if necessary
        $AppID = Get-CimInstance -ComputerName $sccmServer -Namespace root\SMS\Site_$siteCode -Class SMS_PackageBaseclass -Filter "Name='$App'" | select -exp PackageID
        $distributed = Get-CimInstance -ComputerName $sccmServer -Namespace root\SMS\Site_$siteCode -Class SMS_DistributionStatus | where { $_.packageid -eq $AppID }
        if (!$distributed) {
            Write-Warning "Application $App isn't on any DP, distributing to DP group '$DPGroupName'"
            Start-CMContentDistribution -ApplicationName $App -DistributionPointGroupName $DPGroupName
        }

        # create phased deployment
        $null = New-CMApplicationAutoPhasedDeployment -ApplicationName $App -Name "Phased Deployment - $App" `
            -FirstCollectionName $phs1Collection -SecondCollectionName $phs2Collection `
            -CriteriaOption Compliance -CriteriaValue 95 `
            -BeginCondition AfterPeriod -DaysAfterPreviousPhaseSuccess 7 `
            -ThrottlingDays 7 -InstallationChoice AfterPeriod `
            -DeadlineUnit Days -DeadlineValue 3 `
            -Description "$App pilot deployment"
    }
}

function New-CMDevice {
    <#
    .SYNOPSIS
    Function for creating new SCCM device in SCCM database.
 
    .DESCRIPTION
    Function for creating new SCCM device in SCCM database.
 
    .PARAMETER computerName
    Name of the device.
 
    .PARAMETER MACAddress
    MAC address of the device.
 
    .PARAMETER serialNumber
    (optional) Serial number (service tag) of the device.
 
    .PARAMETER collectionName
    (optional) Name of the SCCM collection this device should be member of.
 
    .EXAMPLE
    New-CMDevice -computerName nl-23-ntb -MACAddress 48:51:C5:20:44:2D -serialNumber 1234567
    #>


    param (
        [Parameter(Mandatory = $true)]
        [string] $computerName,

        [Parameter(Mandatory = $true)]
        [string] $MACAddress,

        [ValidatePattern('^[a-z0-9]{7}$')]
        [string] $serialNumber,

        [string[]] $collectionName
    )

    if (!$serialNumber) {
        $choice = ""
        while ($choice -notmatch "^[Y|N]$") {
            $choice = Read-Host "You haven't entered serialNumber. OSD won't work without it! Continue? (Y|N)"
        }
        if ($choice -eq "N") {
            break
        }
    }

    Connect-SCCM -commandName Import-CMComputerInformation -ErrorAction Stop

    $param = @{
        computerName = $computerName
        macAddress   = $MACAddress
    }

    if ($collectionName) { $param.collectionName = $collectionName }

    Import-CMComputerInformation @param

    if ($serialNumber) {
        Set-CMDeviceSerialNumber $computerName $serialNumber
    }
}

function Refresh-CMCollection {
    <#
    .SYNOPSIS
    Function for forcing full membership update on selected (all) device collection(s).
 
    .DESCRIPTION
    Function for forcing full membership update on selected (all) device collection(s).
    Before membership update, full AD discovery is being run.
 
    .PARAMETER collectionName
    Name of collection(s) you want to refresh.
    If not specified all device collections will be refreshed.
 
    .EXAMPLE
    Refresh-CMCollection
 
    Runs full AD discovery and than updates membership of all device collections.
 
    .EXAMPLE
    Refresh-CMCollection -collectionName _workstations
 
    Runs full AD discovery and than updates membership of _workstations collection.
    #>


    [CmdletBinding()]
    param ([string[]] $collectionName)

    # connect to SCCM
    Connect-SCCM -ea Stop

    # run AD discovery
    Invoke-CMGroupDiscovery
    Invoke-CMSystemDiscovery

    "Wait one minute so AD discovery has time to finish"
    Start-Sleep 60

    # update collection(s) membership
    if (!$collectionName) {
        Write-Verbose "Getting device collections"
        $collectionName = Get-CMDeviceCollection | select -exp Name
    }
    $collectionName | % {
        Write-Verbose "Updating collection '$_'"
        Invoke-CMCollectionUpdate -Name $_ -Confirm:$false
    }
}

function Set-CMDeviceDJoinBlobVariable {
    <#
    .SYNOPSIS
    Function for enabling Offline Domain Join in OSD process of given computer.
 
    .DESCRIPTION
    Function for enabling Offline Domain Join in OSD process of given computer.
 
    It will:
     - create Offline Domain Join blob using djoin.exe
     - save resultant blob content as computer variable DJoinBlob
        - so it can be used during OSD for domain join
 
    When the computer connects eventually to one of the DCs. It will automatically reset its password. So generated djoin blob will be invalidated (it contains password, that is being set, when computer joins the domain).
 
    .PARAMETER computerName
    Name of the computer, that should be joined to domain during the OSD.
 
    It doesn't matter, what name it actually has, it will be changed, to this one!
 
    .PARAMETER ou
    OU where should be computer placed (in case it doesn't already exists in AD).
 
    .PARAMETER reuse
    Switch that has to be used in case, such computer already exists in AD.
 
    Its password will be immediately reset!!!
 
    .PARAMETER domainName
    Name of domain.
 
    .EXAMPLE
    Set-CMDeviceDJoinBlobVariable -computerName PC-1 -reuse
 
    Function will generate offline domain join blob for joining computer PC-1.
    This blob will be saved as Task Sequence Variable in properties of given computer.
    In case computer already exists in AD, its password will be immediately reset.
 
    .NOTES
    # jak dat do unattend souboru https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd392267(v=ws.10)?redirectedfrom=MSDN#offline-domain-join-process-and-djoinexe-syntax
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $computerName,

        [ValidateScript( {
                If (Get-ADOrganizationalUnit -Filter "distinguishedname -eq '$_'") {
                    $true
                } else {
                    Throw "$_ is not a valid OU distinguishedName."
                }
            })]
        [string] $ou,

        [switch] $reuse,

        [string] $domainName = $domainName
    )

    begin {
        if (!(Get-Module ActiveDirectory -ListAvailable)) {
            if ((Get-CimInstance win32_operatingsystem -Property caption).caption -match "server") {
                throw "Module ActiveDirectory is missing. Use: Install-WindowsFeature RSAT-AD-PowerShell -IncludeManagementTools" 
            } else {
                throw "Module ActiveDirectory is missing. Use: Get-WindowsCapability -Name RSAT* -Online | Add-WindowsCapability -Online"
            }
        }
    }

    process {
        $adComputer = Get-ADComputer -Filter "name -eq '$computerName'" -Properties Name, Enabled, DistinguishedName -ErrorAction Stop

        #region checks
        if ($reuse -and $adComputer -and $adComputer.Enabled) {
            Write-Warning "Reuse parameter will immediately reset $computerName AD password!"
            $choice = ""
            while ($choice -notmatch "^[Y|N]$") {
                $choice = Read-Host "Continue? (Y|N)"
            }
            if ($choice -eq "N") {
                break
            }
        }

        if (!$adComputer -and !$ou) {
            do {
                $ou = Read-Host "Computer $computerName doesn't exist. Enter existing OU distinguishedName, where it should be created"
            } while (!(Get-ADOrganizationalUnit -Filter "distinguishedname -eq '$ou'"))
        }

        if ($adComputer -and !$reuse) {
            throw "$computerName already exists in AD so 'reuse' parameter has to be used!"
        }

        Connect-SCCM -commandName Get-CMDeviceVariable, Remove-CMDeviceVariable, New-CMDeviceVariable, Get-CMDevice

        $device = Get-CMDevice -Name $computerName
        if (!$device) { throw "$computerName isn't in SCCM database" }
        if ($device.count -gt 1) { throw "There are $($device.count) devices in SCCM database with name $computerName" }
        #endregion checks

        #region create djoin connection blob
        "Creating djoin connection blob"
        $blobFile = (Get-Random)
        # /reuse provede reset computer hesla!
        # /rootcacerts /certtemplate "WorkstationAuthentication-PrimaryTPM"
        $djoinArgument = "/provision /domain $domainName /machine $computerName /savefile $blobFile /printblob"
        if ($reuse) { $djoinArgument += " /reuse" }
        if ($ou) { $djoinArgument += " /machineou $ou" }

        $djoin = Start-Process2 "$env:windir\system32\djoin.exe" -argumentList $djoinArgument

        if (!($djoin -match "The operation completed successfully")) {
            throw $djoin
        }

        # I don't need this file
        Remove-Item $blobFile -Force

        # Get the blob
        $djoinBlob = ($djoin -split "`n")[6].trim()
        if ($djoinBlob -notmatch "=$") { throw "$djoinBlob is not valid djoin blob" }
        #endregion create djoin connection blob

        #region customize SCCM Device DJoinBlob TS Variable
        # variable name that should contain djoin blob for offline domain join
        $variableName = "DJoinBlob"

        "Setting variable '$variableName' for SCCM device $computerName"

        # !Get-CMDeviceVariable is case insensitive, but Set-CMDeviceVariable isn't! therefore I use existing name, just in case
        if ($foundVariableName = Get-CMDeviceVariable -DeviceName $computerName -VariableName $variableName | select -ExpandProperty Name) {
            # variable already exists, delete
            $variableName = $foundVariableName
            Remove-CMDeviceVariable -DeviceName $computerName -VariableName $variableName -Force
        }

        New-CMDeviceVariable -DeviceName $computerName -VariableName $variableName -VariableValue $djoinBlob | Out-Null #-IsMask $true
        #endregion customize SCCM Device DJoinBlob TS Variable
    }

    end {
        Write-Warning "You can use this blob to join any computer, but $computerName will be new computer name, no matter what name computer already has"
    }
}

function Set-CMDeviceSerialNumber {
    <#
    .SYNOPSIS
    Function for setting devices SerialNumber in SCCM database.
 
    .DESCRIPTION
    Function for setting devices SerialNumber in SCCM database.
    Can be used for new, not yet discovered devices or devices where hardware inventory hasn't been run yet.
 
    .PARAMETER computerName
    Name of SCCM device.
 
    .PARAMETER serialNumber
    Serial number (service tag) of the device.
    Should be numbers and letters and length should be 7 chars.
 
    .PARAMETER SCCMServer
    Name of SCCM server.
 
    .PARAMETER SCCMSiteCode
    Name of SCCM site.
 
    .EXAMPLE
    Set-CMDeviceSerialNumber -computerName nl-23-atb -serialNumber 123a123
    #>


    [Alias("Set-CMDeviceServiceTag")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $computerName,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-z0-9]{7}$')]
        [Alias("serviceTag", "srvTag")]
        [string] $serialNumber,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMServer = $_SCCMServer,

        [ValidateNotNullOrEmpty()]
        [string] $SCCMSiteCode = $_SCCMSiteCode
    )

    $ErrorActionPreference = "Stop"

    $serialNumber = $serialNumber.ToUpper()

    $machineID = (Invoke-SQL -dataSource $SCCMServer -database "CM_$SCCMSiteCode" -sqlCommand "SELECT ItemKey FROM vSMS_R_System WHERE Name0 = '$computerName'").ItemKey

    if (!$machineID) { throw "$computerName wasn't found in SCCM database" }

    $machineSQLRecord = Invoke-SQL -dataSource $SCCMServer -database "CM_$SCCMSiteCode" -sqlCommand "SELECT MachineId FROM System_Enclosure_DATA WHERE MachineID = '$machineID'"
    if (!$machineSQLRecord.MachineId) { throw "$computerName doesn't have any record in SQL table System_Enclosure_DATA so there is nothing to update" }

    Invoke-SQL -dataSource $SCCMServer -database "CM_$SCCMSiteCode" -sqlCommand "UPDATE System_Enclosure_DATA SET SerialNumber00 = '$serialNumber' WHERE MachineID = '$machineID'" -force
}

Function Update-CMAppSourceContent {
    <#
    .SYNOPSIS
    Spusti aktualizaci zdrojovych souboru vybranych aplikaci na SCCM serveru.
 
    .DESCRIPTION
    Spusti aktualizaci zdrojovych souboru vybranych aplikaci na SCCM serveru.
    (nahraje je znovu ze zdroje na DP)
 
    .PARAMETER appName
    Jmeno aplikace, jejiz zdrojove soubory se maji updatovat na DP.
    Pokud se nezada, zobrazi se tabulka s dostupnymi aplikacemi.
 
    .PARAMETER sccmServer
    Jmeno sccm serveru.
 
    .PARAMETER siteCode
    Jmeno SCCM site.
 
    .EXAMPLE
    Update-CMAppSourceContent -appName "7-zip x64"
 
    Updatuje zdrojove soubory aplikace 7zip na SCCM serveru.
 
    .EXAMPLE
    Update-CMAppSourceContent
 
    Zobrazi tabulku s dostupnymi aplikacemi, po vybrani a potvrzeni, provede update jejich zdrojovych souboru.
    #>


    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string[]] $appName
        ,
        [ValidateNotNullOrEmpty()]
        $sccmServer = $_SCCMServer
        ,
        [ValidateNotNullOrEmpty()]
        $siteCode = $_SCCMSiteCode
    )

    process {
        # Get-CMApplication nejde pouzit s OGV (fce se ukonci), proto skrze WMI
        $application = @()
        if (!$appName) {
            $appName = Get-CimInstance -ComputerName $sccmServer -Namespace "root\sms\site_$siteCode" -Query 'SELECT * FROM SMS_Application WHERE isexpired="false" AND isenabled="true"' |
            select LocalizedDisplayName | sort LocalizedDisplayName | Out-GridView -OutputMode Multiple | select -exp LocalizedDisplayName
        }

        $appName | % {
            "Ziskavam informace o $_"
            $name = $_
            $app = Get-CimInstance -Namespace "Root\SMS\Site_$siteCode" -Class SMS_ApplicationLatest -ComputerName $sccmServer -Filter "LocalizedDisplayName='$name'"
            $packageID = Get-CimInstance -Namespace "Root\SMS\Site_$siteCode" -Class SMS_ContentPackage -ComputerName $sccmServer -Filter "SecurityKey='$($app.ModelName)'" | select -exp PackageID
            if ($packageID) {
                $application += New-Object PSObject -Property @{ LocalizedDisplayName = $name; PackageId = $packageID }
            } else {
                Write-Warning "U aplikace $name se nepodarilo ziskat packageID, updatujte rucne v SCCM konzoli"
            }

        }

        foreach ($app in $application) {
            "Updatuji zdrojaky $($app.LocalizedDisplayName)"
            $WmiObjectParam = @{
                'Namespace'    = "root\SMS\Site_$siteCode";
                'Class'        = 'SMS_ContentPackage';
                'computername' = $sccmServer;
                'Filter'       = "PackageID='$($app.PackageId)'";
            }
            (Get-CimInstance @WmiObjectParam).Commit() | Out-Null
        }
    }
}

function Update-CMClientPolicy {
    <#
    .SYNOPSIS
    Function for invoking update of SCCM client policy.
 
    .DESCRIPTION
    Function for invoking update of SCCM client policy.
 
    .PARAMETER computerName
    Name of the computer where you want to make update.
 
    .PARAMETER evaluateBaseline
    Switch for invoking evaluation of compliance policies.
 
    .PARAMETER resetPolicy
    Switch for resetting policies (Machine Policy Agent Cleanup).
    #>


    [cmdletbinding()]
    [Alias('Invoke-CMClientPolicyUpdate')]
    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $True, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]$computerName = $env:COMPUTERNAME
        ,
        [switch] $evaluateBaseline
        ,
        [switch] $resetPolicy
    )

    BEGIN {
        if ($env:COMPUTERNAME -in $computerName) {
            if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
                throw 'Run with administrator rights!'
            }
        }

        $allFunctionDefs = "function Invoke-CMComplianceEvaluation { ${function:Invoke-CMComplianceEvaluation} }"
    }

    PROCESS {

        $param = @{
            scriptBlock = {
                param ($resetPolicy, $evaluateBaseline, $allFunctionDefs)

                $ErrorActionPreference = 'stop'
                # list of triggers https://blogs.technet.microsoft.com/charlesa_us/2015/03/07/triggering-configmgr-client-actions-with-wmic-without-pesky-right-click-tools/
                try {
                    foreach ($functionDef in $allFunctionDefs) {
                        . ([ScriptBlock]::Create($functionDef))
                    }

                    if ($resetPolicy) {
                        $null = ([wmiclass]'ROOT\ccm:SMS_Client').ResetPolicy(1)
                        # invoking Machine Policy Agent Cleanup
                        $null = Invoke-CimMethod -Class SMS_client -Namespace 'root\ccm' -Name TriggerSchedule -Arguments '{00000000-0000-0000-0000-000000000040}'
                        Start-Sleep -Seconds 5
                    }
                    # invoking receive of computer policies
                    $null = Invoke-CimMethod -Class SMS_client -Namespace 'root\ccm' -Name TriggerSchedule -Arguments '{00000000-0000-0000-0000-000000000021}'
                    Start-Sleep -Seconds 1
                    # invoking Machine Policy Evaluation Cycle
                    $null = Invoke-CimMethod -Class SMS_client -Namespace 'root\ccm' -Name TriggerSchedule -Arguments '{00000000-0000-0000-0000-000000000022}'
                    if (!$resetPolicy) {
                        # after hard reset I have to wait a little bit before this method can be used again
                        Start-Sleep -Seconds 5
                        # invoking Application Deployment Evaluation Cycle
                        $null = Invoke-CimMethod -Class SMS_client -Namespace 'root\ccm' -Name TriggerSchedule -Arguments '{00000000-0000-0000-0000-000000000121}'
                    }

                    # invoke evaluation of compliance policies
                    if ($evaluateBaseline) {
                        Invoke-CMComplianceEvaluation
                    }

                    Write-Output "Policy update started on $env:COMPUTERNAME"
                } catch {
                    throw "$env:COMPUTERNAME is probably missing SCCM client.`n`n$_"
                }
            }

            ArgumentList = $resetPolicy, $evaluateBaseline, $allFunctionDefs
        }
        if ($computerName -and $computerName -notin 'localhost', $env:COMPUTERNAME) {
            $param.computerName = $computerName
        }

        Invoke-Command @param
    }

    END {
        if ($resetPolicy) {
            Write-Warning 'Is is desirable to run Update-CMClientPolicy again after a few minutes to get new policies ASAP'
        }
    }
}

Export-ModuleMember -function Add-CMDeviceToCollection, Clear-CMClientCache, Connect-SCCM, Get-CMAppDeploymentStatus, Get-CMApplicationOGV, Get-CMAutopilotHash, Get-CMCollectionComplianceStatus, Get-CMCollectionOGV, Get-CMComputerCollection, Get-CMComputerComplianceStatus, Get-CMDeploymentStatus, Get-CMLog, Invoke-CMAdminServiceQuery, Invoke-CMAppInstall, Invoke-CMClientReinstall, Invoke-CMComplianceEvaluation, New-CMAppDeployment, New-CMAppPhasedDeploment, New-CMDevice, Refresh-CMCollection, Set-CMDeviceDJoinBlobVariable, Set-CMDeviceSerialNumber, Update-CMAppSourceContent, Update-CMClientPolicy

Export-ModuleMember -alias Invoke-CMAppDeployment, Invoke-CMAppPhasedDeployment, Invoke-CMClientPolicyUpdate, Set-CMDeviceServiceTag