NTS.Tools.MSConfigMgr.psm1

function Install-WADK {
    <#
        .Description
        this function can be used to install Windows ADK and Windows ADK PE
 
        .Parameter Latest
        use if you want the latest version
 
        .Parameter Features
        a list of Windows ADK to be installed
 
        .Parameter IncludeWinPE
        use if you want windows adk pe installed
 
        .Parameter Outpath
        path where the install and log files are saved
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Install-WADK -Latest -IncludeWinPE -Features OptionId.DeploymentTools, OptionId.UserStateMigrationTool
 
        .NOTES
        requires internet connection
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("latest", "W11_22H2", "W11_21H2", "W10_2004")]
        [string]
        $Version,

        [Parameter(Mandatory = $true)]
        [ValidateSet(
            "OptionId.ApplicationCompatibilityToolkit", 
            "OptionId.DeploymentTools",
            "OptionId.ImagingAndConfigurationDesigner",
            "OptionId.ICDConfigurationDesigner",
            "OptionId.UserStateMigrationTool",
            "OptionId.VolumeActivationManagementTool",
            "OptionId.WindowsPerformanceToolkit",
            "OptionId.UEVTools",
            "OptionId.AppmanSequencer",
            "OptionId.AppmanAutoSequencer",
            "OptionId.MediaeXperienceAnalyzer",
            "OptionId.MediaeXperienceAnalyzer",
            "OptionId.WindowsAssessmentToolkit"
        )]
        [string[]]
        $Features,
        
        [Parameter(Mandatory = $false)]
        [switch]
        $IncludeWinPE,

        [Parameter(Mandatory = $false)]
        [string]
        $Outpath = "$($env:ProgramData)\NTS\Windows_ADK",

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    switch ($Version) {
        { ($PSItem -eq "latest") -or ($PSItem -eq "W11_22H2") } { 
            $WADK_Download_URL = "https://download.microsoft.com/download/6/7/4/674ec7db-7c89-4f2b-8363-689055c2b430/adk/adksetup.exe"
            $WADK_PE_Download_URL = "https://download.microsoft.com/download/5/2/5/525dcde0-c7b8-487a-894d-0952775a78c7/adkwinpeaddons/adkwinpesetup.exe"
        }
        "W11_21H2" { 
            $WADK_Download_URL = "https://download.microsoft.com/download/1/f/d/1fd2291e-c0e9-4ae0-beae-fbbe0fe41a5a/adk/adksetup.exe"
            $WADK_PE_Download_URL = "https://download.microsoft.com/download/5/5/e/55e3e34a-5708-46cd-a90d-92044c29336b/adkwinpeaddons/adkwinpesetup.exe"
        }
        "W10_2004" { 
            $WADK_Download_URL = "https://download.microsoft.com/download/8/6/c/86c218f3-4349-4aa5-beba-d05e48bbc286/adk/adksetup.exe"
            $WADK_PE_Download_URL = "https://download.microsoft.com/download/3/c/2/3c2b23b2-96a0-452c-b9fd-6df72266e335/adkwinpeaddons/adkwinpesetup.exe"
        }
        Default { throw "this version '$($Version)' is not supported" }
    }

    try {
        New-ItemIfNotExists -Path $Outpath -ItemType Directory
        $Outpath = (Get-Item -Path $Outpath).FullName
    
        if ($Features.count -gt 1) {
            $Features | ForEach-Object {
                [string]$Features_Selected = $Features_Selected + " " + $PSItem
            }
        }
    
        $WADK_Path = "$($Outpath)\adksetup-$($Version).exe"
        $WADK_LogPath = "$($Outpath)\install-adksetup-$($Version).log"
        $WADK_PE_Path = "$($Outpath)\adkwinpesetup-$($Version).exe"
        $WADK_PE_LogPath = "$($Outpath)\install-adkwinpesetup-$($Version).log"

        # download
        Write-ToLogOrTerminal @LogParam -Severity Info -Message  "downloading adk setup files"
        Start-FileDownload -DownloadURL $WADK_Download_URL -FileOutPath $WADK_Path

        # install
        Write-ToLogOrTerminal @LogParam -Severity Info -Message  "installing adk with the features $($Features_Selected)"
        $Process = Start-Process -FilePath $WADK_Path -ArgumentList "/quiet /norestart /features $($Features_Selected) /l $($WADK_LogPath)" -NoNewWindow -Wait -PassThru
        if ($Process.ExitCode -ne 0) {
            throw "check log at $($WADK_LogPath)"
        }
    
        if ($IncludeWinPE -eq $true) {
            # download
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "downloading adk pe setup files"
            Start-FileDownload -DownloadURL $WADK_PE_Download_URL -FileOutPath $WADK_PE_Path
    
            # install
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "installing adk pe"
            $Process = Start-Process -FilePath $WADK_PE_Path -ArgumentList "/quiet /norestart /features OptionId.WindowsPreinstallationEnvironment /l $($WADK_PE_LogPath)" -NoNewWindow -Wait -PassThru
            if ($Process.ExitCode -ne 0) {
                throw "check log at $($WADK_PE_LogPath)"
            }
        }

        Start-FolderCleanUp -FolderToRemove $Outpath
    }
    catch {
        $ErrorMessage = "something went wrong $($PSItem.Exception.Message)"
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $ErrorMessage
        throw $ErrorMessage
    }
}

function Initialize-CM_MP_Prereq {
    <#
        .Description
        use this function to install configmgr management point prerequesits
 
        .Example
        Initialize-CM_MP_Prereq
 
        .NOTES
         
    #>


    $Features = @(
        "NET-Framework-Core"
        "FileAndStorage-Services"
        "Storage-Services"
        "Web-Server"
        "Web-WebServer"
        "Web-Common-Http"
        "Web-Default-Doc"
        "Web-Dir-Browsing"
        "Web-Http-Errors"
        "Web-Static-Content"
        "Web-Http-Redirect"
        "Web-DAV-Publishing"
        "Web-Health"
        "Web-Http-Logging"
        "Web-Custom-Logging"
        "Web-Log-Libraries"
        "Web-ODBC-Logging"
        "Web-Request-Monitor"
        "Web-Http-Tracing"
        "Web-Performance"
        "Web-Stat-Compression"
        "Web-Dyn-Compression"
        "Web-Security"
        "Web-Filtering"
        "Web-Basic-Auth"
        "Web-CertProvider"
        "Web-Client-Auth"
        "Web-Digest-Auth"
        "Web-Cert-Auth"
        "Web-IP-Security"
        "Web-Url-Auth"
        "Web-Windows-Auth"
        "Web-App-Dev"
        "Web-Net-Ext"
        "Web-Net-Ext45"
        "Web-AppInit"
        "Web-ASP"
        "Web-Asp-Net"
        "Web-Asp-Net45"
        "Web-CGI"
        "Web-ISAPI-Ext"
        "Web-ISAPI-Filter"
        "Web-Includes"
        "Web-WebSockets"
        "Web-Ftp-Server"
        "Web-Ftp-Service"
        "Web-Ftp-Ext"
        "Web-Mgmt-Tools"
        "Web-Mgmt-Console"
        "Web-Mgmt-Compat"
        "Web-Metabase"
        "Web-Lgcy-Mgmt-Console"
        "Web-Lgcy-Scripting"
        "Web-WMI"
        "Web-Scripting-Tools"
        "Web-Mgmt-Service"
        "NET-Framework-Features"
        "NET-Framework-Core"
        "NET-Framework-45-Features"
        "NET-Framework-45-Core"
        "NET-Framework-45-ASPNET"
        "NET-WCF-Services45"
        "NET-WCF-HTTP-Activation45"
        "NET-WCF-MSMQ-Activation45"
        "NET-WCF-Pipe-Activation45"
        "NET-WCF-TCP-Activation45"
        "NET-WCF-TCP-PortSharing45"
        "BITS"
        "BITS-IIS-Ext"
        "BITS-Compact-Server"
        "MSMQ"
        "MSMQ-Services"
        "MSMQ-Server"
        "Windows-Defender"
        "RDC"
        "RSAT"
        "RSAT-Feature-Tools"
        "RSAT-Bits-Server"
        "System-DataArchiver"
        "PowerShellRoot"
        "PowerShell"
        "PowerShell-V2"
        "WAS"
        "WAS-Process-Model"
        "WAS-Config-APIs"
        "WoW64-Support"
        "XPS-Viewer"
    )

    try {
        Write-Output "installing required features for configmgr management point"
        Install-WindowsFeature -Name $Features | Out-Null
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Initialize-CM_DP_Prereq {
    <#
        .Description
        use this function to install configmgr distribution point prerequesits
 
        .Example
        Initialize-CM_DP_Prereq
 
        .NOTES
         
    #>


    $Features = @(
        "FileAndStorage-Services"
        "File-Services"
        "FS-FileServer"
        "Storage-Services"
        "Web-Server"
        "Web-WebServer"
        "Web-Common-Http"
        "Web-Default-Doc"
        "Web-Dir-Browsing"
        "Web-Http-Errors"
        "Web-Static-Content"
        "Web-Http-Redirect"
        "Web-Health"
        "Web-Http-Logging"
        "Web-Performance"
        "Web-Stat-Compression"
        "Web-Security"
        "Web-Filtering"
        "Web-Windows-Auth"
        "Web-App-Dev"
        "Web-ISAPI-Ext"
        "Web-Mgmt-Tools"
        "Web-Mgmt-Console"
        "Web-Mgmt-Compat"
        "Web-Metabase"
        "Web-WMI"
        "Web-Scripting-Tools"
        "NET-Framework-45-Features"
        "NET-Framework-45-Core"
        "NET-WCF-Services45"
        "NET-WCF-TCP-PortSharing45"
        "Windows-Defender"
        "RDC"
        "System-DataArchiver"
        "PowerShellRoot"
        "PowerShell"
        "WoW64-Support"
        "XPS-Viewer"
    )

    try {
        if ((Test-Path -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS") -eq $false) {
            Write-Output "creating NO_SMS_ON_DRIVE.SMS on boot volume"
            New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null
        }
        else {
            Write-Output "file NO_SMS_ON_DRIVE.SMS on boot volume already exists"
        }
        Write-Output "installing required features for configmgr distribution point"
        Install-WindowsFeature -Name $Features | Out-Null
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Initialize-CM_SiteServer_Prereq {
    <#
        .Description
        use this function to install configmgr site server prerequesits
 
        .Example
        Initialize-CM_SiteServer_Prereq
 
        .NOTES
         
    #>


    $Features = @(
        "RDC"
        "UpdateServices-RSAT"
        "NET-Framework-Features"
    )

    try {
        if ((Test-Path -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS") -eq $false) {
            Write-Output "creating NO_SMS_ON_DRIVE.SMS on boot volume"
            New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null
        }
        else {
            Write-Output "file NO_SMS_ON_DRIVE.SMS on boot volume already exists"
        }
        Write-Output "installing required features for configmgr site server"
        Install-WindowsFeature -Name $Features -IncludeAllSubFeature | Out-Null  
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Initialize-CM_SUP_Prereq {
    <#
        .Description
        use this function to install configmgr software update point prerequesits
 
        .Example
        Initialize-CM_SUP_Prereq
 
        .NOTES
         
    #>


    try {
        if ((Test-Path -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS") -eq $false) {
            Write-Output "creating NO_SMS_ON_DRIVE.SMS on boot volume"
            New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null
        }
        else {
            Write-Output "file NO_SMS_ON_DRIVE.SMS on boot volume already exists"
        }
        Write-Output "installing required features for configmgr software update point"
        Install-WindowsFeature -Name RDC, UpdateServices-RSAT -IncludeAllSubFeature | Out-Null
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Add-CM_ADContainer {
    <#
        .Description
        use this function to create the system management container in ad and add permissions to the local server
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Add-CM_ADContainer
 
        .NOTES
        this should be run on the site system server
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        Import-Module -Name "ActiveDirectory"
        $AD_DistinguishedName = (Get-ADDomain).DistinguishedName
        $CM_ContainerName = "SYSTEM MANAGEMENT"
    
        Write-ToLogOrTerminal @LogParam -Severity Info -Message  "adding container 'SYSTEM MANAGEMENT'"
        if ($null -eq (Get-ADObject -Filter 'ObjectClass -eq "container"' -SearchBase "CN=System,$($AD_DistinguishedName)" | Where-Object -Property Name -eq $CM_ContainerName)) {
            New-ADObject -Name $CM_ContainerName -Path "CN=System,$($AD_DistinguishedName)" -Type Container
        }
    
        Write-ToLogOrTerminal @LogParam -Severity Info -Message  "adding permissions for the ad container"
        $path = "AD:\CN=$($CM_ContainerName),CN=System,$($AD_DistinguishedName)"
        $ADCompObject = Get-ADComputer -Identity $env:COMPUTERNAME
        
        $adRights = [DirectoryServices.ActiveDirectoryRights]::GenericAll
        $accessType = [Security.AccessControl.AccessControlType]::Allow
        $inheritance = [DirectoryServices.ActiveDirectorySecurityInheritance]::All
        $fullAccessACE = New-Object -TypeName DirectoryServices.ActiveDirectoryAccessRule -ArgumentList @($ADCompObject.SID, $adRights, $accessType, $inheritance)
        
        $acl = Get-Acl -Path $path
        $acl.AddAccessRule($fullAccessACE)
        Set-Acl -Path $path -AclObject $acl
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Install-WSUS {
    <#
        .Description
        this will install the required functions for wsus and do the post install tasks
 
        .Parameter UseWID
        wsus with windows internal database
 
        .Parameter UseSQL
        wsus with mssql database
 
        .Parameter WSUSFilePath
        where should the file be stored
 
        .Parameter SQLInstance
        sql instance for wsus
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Set-Interface -InterfaceObject $SFP10G_NICs[0] -IPAddress $CLU1_IPAddress -NetPrefix $NetPrefix -DefaultGateway $CLU_DefaultGateway -DNSAddresses $CLU_DNSAddresses -NewName "Datacenter-1"
 
        .NOTES
        https://smsagent.blog/2014/02/07/installing-and-configuring-wsus-with-powershell/
    #>


    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName = 'WID')]
        [switch]
        $UseWID,

        [Parameter(ParameterSetName = 'SQL')]
        [switch]
        $UseSQL,

        [Parameter(ParameterSetName = 'WID', Mandatory = $true)]
        [Parameter(ParameterSetName = 'SQL', Mandatory = $true)]
        [string]
        $WSUSFilePath,

        [Parameter(ParameterSetName = 'SQL', Mandatory = $true)]
        [string]
        $SQLInstance, # "MyServer\MyInstance"

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    New-ItemIfNotExists -Path $WSUSFilePath -ItemType Directory
    try {
        if ($UseWID -eq $true) {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "installing required features for wsus"
            Install-WindowsFeature "UpdateServices" -IncludeManagementTools -WarningAction "SilentlyContinue" | Out-Null
    
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "doing postinstall with wid"
            Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall CONTENT_DIR=$($WSUSFilePath)" -NoNewWindow -Wait
        }
        elseif ($UseSQL -eq $true) {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "installing required features for wsus"
            Install-WindowsFeature -Name "UpdateServices-Services", "UpdateServices-DB" -IncludeManagementTools -WarningAction "SilentlyContinue" | Out-Null
    
            Write-ToLogOrTerminal @LogParam -Severity Info -Message  "doing postinstall with sql instance $($SQLInstance)"
            Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall SQL_INSTANCE_NAME=$($SQLInstance) CONTENT_DIR=$($WSUSFilePath)"  -NoNewWindow -Wait
        }
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Confirm-CM_Prerequisites {
    <#
        .Description
        this function will search for the configmgr install volume and run the prerequisite checks for a site server
 
        .Parameter PrereqchkFilePath
        path to the prereqchk.exe
 
        .Parameter CM_SiteServerFQDN
        fqdn of the site server
 
        .Parameter CM_SQL_Site_Instance
        database server with instance name, eg. <fqdn of the site server>\<instancename>
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Confirm-CM_Prerequisites -CM_SiteServerFQDN $CM_SiteServerFQDN -CM_SQL_Site_Instance ($CM_SiteServerFQDN + "\" + $using:CM_SQL_Site_InstanceName)
 
        .NOTES
        https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/prerequisite-checker
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $PrereqchkFilePath,

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

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

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    $CM_PrereqchkLogFilePath = "$($env:SystemDrive)\ConfigMgrPrereq.log"

    try {
        if ($PrereqchkFilePath -eq "") {
            $CM_SetupVolumes = Get-CM_Setup_Volume
            if ($CM_SetupVolumes.DriveLetter.count -eq 1) {
                $CM_Prereqchk_Filepath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\prereqchk.exe"
            }
            else {
                throw "there are more than one or less than one installation media for configmgr"
            }
        }
        else {
            if (Test-Path -Path $PrereqchkFilePath) {
                $CM_Prereqchk_Filepath = $PrereqchkFilePath
            }
            else {
                throw "cannot find prereqchk.exe at $($PrereqchkFilePath)"
            }
        }
    
        if (Test-Path -Path $CM_PrereqchkLogFilePath) {
            Remove-Item -Path $CM_PrereqchkLogFilePath -Force | Out-Null
        }
        
        try {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "checking prerequisites for the site server role & admin console"
            Start-Process -FilePath $CM_Prereqchk_Filepath -ArgumentList "/NOUI /PRI /SDK $($CM_SiteServerFQDN) /SQL $CM_SQL_Site_Instance /SCP" -Wait -NoNewWindow
            Start-Process -FilePath $CM_Prereqchk_Filepath -ArgumentList "/NOUI /ADMINUI" -Wait -NoNewWindow
        }
        catch {
            throw "failed to run $($CM_Prereqchk_Filepath) - $($PSItem.Exception.Message)"
        }
    
        $Content = Get-Content -Path $CM_PrereqchkLogFilePath
        $SuccessMessage = $Content -like "*Prerequisite checking is completed.*"
        $FailureMessage = $Content -like "*ERROR:*"
        if ($null -eq $SuccessMessage[0] -and $null -ne $FailureMessage[0]) {
            if ($FailureMessage -like "*ERROR: Failed to connect to SQL Server 'master' db.*" -and $FailureMessage.Count -gt 2) {
                throw "found errors in log $($CM_PrereqchkLogFilePath):`n$($FailureMessage)"
            }
        }
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "all prerequisites are met for configmgr installation"   
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Uninstall-ConfigMgrAgent {
    <#
        .Description
        this function uninstalls the configmgr agent
 
        .Example
        Uninstall-ConfigMgrAgent
 
        .NOTES
        https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/prerequisite-checker
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [bool]
        $SkipCleanup = $false
    )

    try {
        $CCMExecServiceName = "CcmExec"
        $CCMSetupFilePath = "$($env:windir)\ccmsetup\ccmsetup.exe"
        if ($null -ne (Get-Service -Name $CCMExecServiceName -ErrorAction SilentlyContinue) -or (Test-Path -Path $CCMSetupFilePath)) {
            Write-Output "starting configmgr Agent uninstall"
            Start-Process -FilePath $CCMSetupFilePath -ArgumentList "/uninstall" -Wait -NoNewWindow

            $LogFileContent = Get-Content -Path "$($env:windir)\ccmsetup\logs\CCMSetup.log"
            $SuccesMessage = $LogFileContent -like "*[LOG[Uninstall succeeded.]LOG]*"
        }
        else {
            Write-Output "Service $($CCMExecServiceName) not found and no ccmsetup.exe, skipping"
            $SkipCleanup = $true
        }
    }
    catch {
        throw "error while uninstalling the agent - $($PSItem.Exception.Message)"
    }
    try {
        if ($SkipCleanup -eq $false) {
            if ($SuccesMessage.Count -gt 0) {
                Write-Output "finished configmgr Agent uninstall"
                Write-Output "doing cleanup"
                if (Test-Path -Path "$($env:windir)\CCM") {
                    $Items = Get-ChildItem -Path "$($env:windir)\CCM" 
                    $Items | ForEach-Object {
                        if ((Test-FileLock -Path $PSItem.FullName) -ne $true) {
                            Remove-Item -Path $PSItem.FullName -Force -Recurse | Out-Null
                        }
                    }
                }
                if (Test-Path -Path "$($env:windir)\ccmsetup") {
                    Remove-Item -Path "$($env:windir)\ccmsetup" -Force -Recurse | Out-Null
                }
                Write-Output "finished doing cleanup"
            }
            else {
                throw "uninstall was not successful $($PSItem.Exception.Message)"
            }
        }

        if (Test-RebootPending) {
            Write-Output "reboot to complete uninstall"
        }
    }
    catch {
        throw "error doing cleanup - $($PSItem.Exception.Message)"
    }
}

function Get-CM_Setupfiles {
    <#
        .Description
        downloads the eval setup of configmgr current branch
 
        .Parameter Version
        version of the iso
 
        .Parameter Outpath
        path where the setup file is stored
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Get-CM_Setupfiles -Version 2303 -Outpath $Outpath
 
        .NOTES
        downloads the configmgr current branch eval setup
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateSet("current", "2303")]
        [string]
        $Version = "current",

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

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    switch ($Version) {
        "current" { $DownloadURL = "https://go.microsoft.com/fwlink/p/?LinkID=2195628&clcid=0x409&culture=en-us&country=us" }
        "2303" { $DownloadURL = "https://download.microsoft.com/download/0/0/1/001d97e2-c427-4d4b-ad30-1556ee0ff1b0/MCM_Configmgr_2303.exe?culture=en-us&country=us" }
        Default { throw "no version was selected" }
    }
    
    New-ItemIfNotExists -Path $Outpath -ItemType Directory
    $Outpath = (Get-Item -Path $Outpath).FullName
    $SetupPath = "ConfigMgr-$($Version)-CB-Eval.exe"
    $SetupFullPath = "$($Outpath)\$($SetupPath)"
    
    try {
        Start-FileDownload -DownloadURL $DownloadURL -FileOutPath $SetupFullPath
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "finished download, starting extraction to $($Outpath)"
        Start-Process -FilePath $SetupFullPath -ArgumentList "-s" -NoNewWindow -Wait -WorkingDirectory $Outpath

        $FoldersFound = Get-ChildItem -Path $Outpath | Where-Object -Property Attributes -Like "*Directory*"
        $FolderWithSetup = $FoldersFound | ForEach-Object {
            $Items = Test-Path -Path "$($PSItem.FullName)\SMSSETUP\BIN\X64\setup.exe"
            if ($Items) {
                return $PSItem
            }
        }
        if ($FolderWithSetup.count -gt 1) {
            throw "found more than one folder with the setup files"
        }    
        Rename-Item -Path $FolderWithSetup.FullName -NewName SetupFiles    
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "finished, setup files can be found at $($Outpath)\SetupFiles"
    }
    catch {
        $ErrorMessage = "error downloading eval setup - $($PSItem.Exception.Message)"
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $ErrorMessage
        throw $ErrorMessage
    }
}

function Get-CM_PrerequisiteFiles {
    <#
        .Description
        this function calls SMSSETUP\BIN\X64\Setupdl.exe from configmgr iso
 
        .Parameter SetupdlFilePath
        path to the Setupdl.exe
 
        .Parameter Outpath
        save path of the downloaded files
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Get-CM_PrerequisiteFiles -SetupdlFilePath "$($using:LocalConfigMgrSetupPath)\SMSSETUP\BIN\X64\setupdl.exe" -Outpath $PrerequisitePath
 
        .NOTES
        https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/setup-downloader
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $SetupdlFilePath,

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

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        New-ItemIfNotExists -Path $Outpath -ItemType Directory
        $Outpath = Resolve-Path $Outpath
        $LogFilePath = "$($env:SystemDrive)\ConfigMgrSetup.log"
        if ($SetupdlFilePath -eq "") {
            $CM_SetupVolumes = Get-CM_Setup_Volume
            if ($CM_SetupVolumes.DriveLetter.count -eq 1) {
                $CM_SetupFileDownloaderPath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\Setupdl.exe"
            }
            else {
                throw "there are more than one or less than one installation media for configmgr"
            }
        }
        else {
            if (Test-Path -Path $SetupdlFilePath) {
                $CM_SetupFileDownloaderPath = $SetupdlFilePath
            }
            else {
                throw "cannot find Setupdl.exe at $($SetupdlFilePath)"
            }
        }
    
        if (Test-Path -Path $LogFilePath) {
            Remove-Item -Path $LogFilePath -Force | Out-Null
        }
            
        try {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "starting download of configmgr setup prerequisite files"
            Start-Process -FilePath $CM_SetupFileDownloaderPath -ArgumentList "/NoUI $($Outpath)" -Wait -NoNewWindow
        }
        catch {
            throw "failed to run $($LogFilePath) - $($PSItem.Exception.Message)"
        }
    
        $Content = Get-Content -Path $LogFilePath
        $SuccessMessage = $Content -like "*INFO: Setup downloader * FINISHED*"
        if ($null -eq $SuccessMessage[0]) {
            throw "no success message, check log $($LogFilePath)"
        }
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "finished download of configmgr setup files"
    }
    catch {
        $ErrorMessage = "error downloading configmgr setup files - $($PSItem.Exception.Message)"
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $ErrorMessage
        throw $ErrorMessage
    }
}

function Get-CM_Setup_Volume {
    <#
        .Description
        this function searches all volumes for the setup.exe from the configmgr iso
 
        .Example
        Uninstall-ConfigMgrAgent
 
        .NOTES
         
    #>


    try {
        $Volumes = Get-Volume | Where-Object -FilterScript { $PSItem.DriveLetter -NE "C" -and $null -ne $PSItem.DriveLetter }
        $Volumes | ForEach-Object {
            if (Test-Path -Path "$($PSItem.DriveLetter):\SMSSETUP\BIN\X64\setup.exe") {
                return $PSItem
            }
        }        
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Initialize-CM_Schema_To_AD {
    <#
        .Description
        this function extends the schema using extadsch.exe for the configmgr
 
        .Parameter ExtadschFilePath
        path to the extadsch.exe
 
        .Example
        Initialize-CM_Schema_To_AD
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .NOTES
        should be run on the siteserver with domain admin privileges
        temporarily the current user is added to the schema admin group
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $ExtadschFilePath,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        if (((Get-ADGroupMember -Identity 'Schema Admins').Name -eq $env:USERNAME) -ne $true) {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "adding current user to schema admins"
            Add-ADGroupMember -Identity 'Schema Admins' -Members $env:USERNAME
        }

        if ($ExtadschFilePath -eq "") {
            $CM_SetupVolumes = Get-CM_Setup_Volume
            if ($CM_SetupVolumes.DriveLetter.count -eq 1) {
                $CM_Extadsch_Filepath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\extadsch.exe"
            }
            else {
                throw "there are more than one or less than one installation media for configmgr"
            }
        }
        else {
            if (Test-Path -Path $ExtadschFilePath) {
                $CM_Extadsch_Filepath = $ExtadschFilePath
            }
            else {
                throw "cannot find extadsch.exe at $($ExtadschFilePath)"
            }
        }
        
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "extending schema"
        Start-Process -FilePath $CM_Extadsch_Filepath -Wait -NoNewWindow

        $LogFilePath = "$($env:SystemDrive)\ExtADSch.log"
        $LogFileContent = Get-Content -Path $LogFilePath
        $SuccessMessage = $LogFileContent -like "*Successfully extended the Active Directory schema.*"
        $FailedMessages = $LogFileContent -like "*Failed to create*"

        if ($null -ne $SuccessMessage[0]) {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "finished extending schema"
        }
        else {
            throw "something went wrong, check the log at $($LogFilePath):`n$($FailedMessages[0])"
        }

        if (((Get-ADGroupMember -Identity 'Schema Admins').Name -eq $env:USERNAME) -ne $true) {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "removing current user from schema admins"
            Remove-ADGroupMember -Identity 'Schema Admins' -Members $env:USERNAME -Confirm:$false
        }
    }
    catch {
        throw "error extending schema - $($PSItem.Exception.Message)"
    }
}

function Install-CM_SiteServer {
    <#
        .Description
        this function extends the schema using extadsch.exe for the configmgr
 
        .Parameter SetupPath
        path to the Setup.exe
 
        .Parameter SiteName
        FriendlyName of the Site
 
        .Parameter SiteCode
        sitecode
 
        .Parameter PrerequisitePath
        path to prerequisite files
 
        .Parameter SQLServer
        fqdn of the sql server, can be the local server
 
        .Parameter SQLInstanceName
        name of the instance
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .Example
        Install-CM_SiteServer -SiteName $CM_SiteName `
            -SiteCode $CM_SiteCode `
            -PrerequisitePath $PrerequisitePath `
            -SQLServer $CM_Site_SQLServer `
            -SQLInstanceName $CM_SQL_Site_InstanceName
 
        .NOTES
        https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/command-line-options-for-setup
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $SetupPath,

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

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

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

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

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

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        $SiteServer = $($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
        $SetupIniPath = "$($env:ProgramData)\NTS\ConfigMgr\SetupConfig.ini"
        $LogFilePath = "$($env:SystemDrive)\ConfigMgrSetup.log"

        $ConfigurationIni = "[Identification]
Action=InstallPrimarySite
CDLatest=0
 
[Options]
ProductID=Eval
SiteCode=$($SiteCode)
SiteName=$($SiteName)
SMSInstallDir=$($env:SystemDrive)\Program Files\Microsoft Configuration Manager
SDKServer=$($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
PrerequisiteComp=1
PrerequisitePath=$($PrerequisitePath)
AdminConsole=1
JoinCEIP=0
MobileDeviceLanguage=0
 
RoleCommunicationProtocol=HTTPorHTTPS
ClientsUsePKICertificate=0
                 
[SQLConfigOptions]
SQLServerName=$($SQLServer + "\" + $SQLInstanceName)
DatabaseName=$("CM_" + $SiteCode)
                 
[CloudConnectorOptions]
CloudConnector=1
CloudConnectorServer=$($SiteServer)
UseProxy=0
                 
[SABranchOptions]
SAActive=0
CurrentBranch=1
"


        New-Item -Path $SetupIniPath -ItemType File -Force | Out-Null
        Set-Content -Path $SetupIniPath -Value $ConfigurationIni

        Write-ToLogOrTerminal @LogParam -Severity Info -Message "starting configmgr site server installtion"
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "to see the progress please view this log $($env:SystemDrive)\ConfigMgrSetup.log on the site server"
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "this can take a while"

        if ($SetupPath -eq "") {
            $CM_SetupVolumes = Get-CM_Setup_Volume
            if ($CM_SetupVolumes.DriveLetter.count -eq 1) {
                Start-Process -FilePath "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\setup.exe" -ArgumentList "/SCRIPT $($SetupIniPath)" -Wait -NoNewWindow
            }
            else {
                throw "there are more than one or less than one installation media for configmgr"
            }
        }
        else {
            if (Test-Path -Path $SetupPath) {
                Start-Process -FilePath $SetupPath -ArgumentList "/SCRIPT $($SetupIniPath)" -Wait -NoNewWindow
            }
            else {
                throw "cannot find the setup file at $($SetupPath)"
            }
        }

        $Content = Get-Content -Path $LogFilePath
        $SuccessMessage = $Content -like "*~===================== Completed Configuration Manager Server Setup =====================*"
        if ($null -eq $SuccessMessage[0]) {
            throw "no success message, check log $($LogFilePath)"
        }
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "finished the configmgr site server installtion"
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Convert-CMSiteUpdateState {
    <#
        .DESCRIPTION
        this function can be used to convert configmgr update status into a string
 
        .Parameter State
        current state of the configmgr site update
 
        .EXAMPLE
        Convert-CMSiteUpdateState -State (Get-CMSiteUpdate -Fast -Name $UpdateName).State
 
        .NOTES
        source: https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/setup-migrate-backup-recovery/understand-troubleshoot-updates-servicing#complete-list-of-state-codes
    #>

    
    # https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/setup-migrate-backup-recovery/understand-troubleshoot-updates-servicing#complete-list-of-state-codes

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $State
    )

    switch ($State) {
        "" { $Message = "no state was provided" }
        "2" { $Message = "UNKNOWN" }
        "0x0" { $Message = "UNKNOWN" }
        "0x2" { $Message = "ENABLED" }
        "262145" { $Message = "DOWNLOAD_IN_PROGRESS" }
        "262146" { $Message = "DOWNLOAD_SUCCESS" }
        "327679" { $Message = "DOWNLOAD_FAILED" }
        "327681" { $Message = "APPLICABILITY_CHECKING" }
        "327682" { $Message = "APPLICABILITY_SUCCESS" }
        "393213" { $Message = "APPLICABILITY_HIDE" }
        "393214" { $Message = "APPLICABILITY_NA" }
        "393215" { $Message = "APPLICABILITY_FAILED" }
        "65537" { $Message = "CONTENT_REPLICATING" }
        "65538" { $Message = "CONTENT_REPLICATION_SUCCESS" }
        "131071" { $Message = "CONTENT_REPLICATION_FAILED" }
        "131073" { $Message = "PREREQ_IN_PROGRESS" }
        "131074" { $Message = "PREREQ_SUCCESS" }
        "131075" { $Message = "PREREQ_WARNING" }
        "196607" { $Message = "PREREQ_ERROR" }
        "196609" { $Message = "INSTALL_IN_PROGRESS" }
        "196610" { $Message = "INSTALL_WAITING_SERVICE_WINDOW" }
        "196611" { $Message = "INSTALL_WAITING_PARENT" }
        "196612" { $Message = "INSTALL_SUCCESS" }
        "196613" { $Message = "INSTALL_PENDING_REBOOT" }
        "262143" { $Message = "INSTALL_FAILED" }
        "196614" { $Message = "INSTALL_CMU_VALIDATING" }
        "196615" { $Message = "INSTALL_CMU_STOPPED" }
        "196616" { $Message = "INSTALL_CMU_INSTALLFILES" }
        "196617" { $Message = "INSTALL_CMU_STARTED" }
        "196618" { $Message = "INSTALL_CMU_SUCCESS" }
        "196619" { $Message = "INSTALL_WAITING_CMU" }
        "262142" { $Message = "INSTALL_CMU_FAILED" }
        "196620" { $Message = "INSTALL_INSTALLFILES" }
        "196621" { $Message = "INSTALL_UPGRADESITECTRLIMAGE" }
        "196622" { $Message = "INSTALL_CONFIGURESERVICEBROKER" }
        "196623" { $Message = "INSTALL_INSTALLSYSTEM" }
        "196624" { $Message = "INSTALL_CONSOLE" }
        "196625" { $Message = "INSTALL_INSTALLBASESERVICES" }
        "196626" { $Message = "INSTALL_UPDATE_SITES" }
        "196627" { $Message = "INSTALL_SSB_ACTIVATION_ON" }
        "196628" { $Message = "INSTALL_UPGRADEDATABASE" }
        "196629" { $Message = "INSTALL_UPDATEADMINCONSOLE" }
        Default { $Message = "could not map the state '$($State)' to state message" }
    }
    return $Message
}

function Get-ConfigMgrSiteUpdate {
    <#
        .DESCRIPTION
        fetches the information for a configmgr site update
 
        .Parameter UpdateName
        name of the update
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .EXAMPLE
        Get-ConfigMgrSiteUpdate -Updatename $UpdateName
         
        .NOTES
 
    #>


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

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal
    
    try {
        do {
            try {
                $CMSiteUpdate = Get-CMSiteUpdate -Name $UpdateName -Fast
            }
            catch {
                if ($PSItem.Exception.Message -like "*The SMS Provider reported an error*") {
                    Write-ToLogOrTerminal @LogParam -Severity Info -Message "waiting on sms provider"
                }
                else {
                    throw $PSItem.Exception.Message
                }
            }
            Start-Sleep -Seconds 10
        } 
        while ( $Null -eq $CMSiteUpdate)

        return $CMSiteUpdate
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Write-CMSiteUpdateStatus {
    <#
        .DESCRIPTION
        shows current status of the update
 
        .Parameter UpdateObj
        A single object returned from get-cmsiteupdate
 
        .Parameter Detailed
        turns on more detailed output of the current status
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .EXAMPLE
        Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed
 
        .NOTES
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Object]
        $UpdateObj,

        [Parameter(Mandatory = $false)]
        [switch]
        $Detailed,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        try {
            $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State
            if ($Detailed) {
                $DetailStatusMessages = $UpdateObj | Get-CMSiteUpdateInstallStatus -Complete -Step All | Select-Object -Last 5 -Property Progress, orderid, SubStageName, Description | Format-Table -AutoSize
            }
        }
        catch {
            if ($PSItem.Exception.Message -like "*The SMS Provider reported an error.*") {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message "waiting on sms provider"
            }
            else {
                throw $PSItem.Exception.Message
            }
        }
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "$($UpdateName) - current status $($StatusMessage)"
        if ($Detailed -and $null -ne $DetailStatusMessages -and $DetailStatusMessages -ne "") {
            $DetailStatusMessages
        }
    }
    catch {
        $ErrorMessage = "error checking status - $($PSItem.Message.Exception)"
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $ErrorMessage
        throw $ErrorMessage
    }
}

function Confirm-CMSiteUpdatePackageDownloaded {
    <#
        .DESCRIPTION
        writes the current status of configmgr site update until the update is downloaded
 
        .Parameter UpdateName
        name of the configmgr site update
 
        .Parameter Detailed
        turns on more detailed output of the current status
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .EXAMPLE
        Confirm-CMSiteUpdatePackageDownloaded -UpdateName $UpdateToInstall.Name
 
        .NOTES
 
    #>


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

        [Parameter(Mandatory = $false)]
        [switch]
        $Detailed,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        # Define update check variables
        $CheckCount = 0
        $ServiceName = "SMS_EXECUTIVE"

        Write-ToLogOrTerminal @LogParam -Severity Info -Message "verifying update $($UpdateName) is downloaded"

        $StoppingStatus = "DOWNLOAD_SUCCESS", "UNKNOWN"

        do {
            if ($Detailed) {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message  "---"
            }
            
            $UpdateCheckStart = Get-Date
            $CheckCount++

            $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName
            $CMUpdateStatus = Convert-CMSiteUpdateState -State $UpdateObj.State
            if ($CMUpdateStatus -ne "DOWNLOAD_SUCCESS") {
                if ($Detailed) {
                    Write-ToLogOrTerminal @LogParam -Severity Info -Message "times checked: $($CheckCount)"
                    Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed
                }
                else {
                    Write-CMSiteUpdateStatus -UpdateObj $UpdateObj
                }
                if ($CheckCount -eq 40) {
                    Write-ToLogOrTerminal @LogParam -Severity Info -Message "downloading state detected for longer than $(((Get-Date) - $UpdateCheckStart).Minutes) minutes, restarting $($ServiceName) service"
                    Restart-Service -Name $ServiceName -Force -Verbose:$false
                }
            }
            else {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message "update package $($UpdateName) is available - current status: $($CMUpdateStatus)"
            }
            if ($CheckCount -ge 150) {
                throw "update is not available, please check manually"
            }
            Start-Sleep -Seconds 15
        }
        while ($StoppingStatus -notcontains $CMUpdateStatus)
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Confirm-CMSiteUpdatePrereqCheckFinished {
    <#
        .DESCRIPTION
        writes the current status of configmgr site update until the prereq checks are finished
 
        .Parameter UpdateName
        name of the configmgr site update
 
        .Parameter Detailed
        turns on more detailed output of the current status
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .EXAMPLE
        Confirm-CMSiteUpdatePrereqCheckFinished -UpdateName $UpdateToInstall.Name
 
        .NOTES
 
    #>


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

        [Parameter(Mandatory = $false)]
        [switch]
        $Detailed,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    try {
        # Define update check variables
        $CheckCount = 0
        $ServiceName = "SMS_EXECUTIVE"
        $StoppingStatus = "PREREQ_ERROR", "PREREQ_WARNING", "INSTALL_IN_PROGRESS"

        Write-ToLogOrTerminal @LogParam -Severity Info -Message "verifying prerequisite checks for update $($UpdateName) are finished"

        do {
            if ($Detailed) {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message "---"
            }
            $UpdateCheckStart = Get-Date
            $CheckCount++

            $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName
            $CMUpdateStatus = Convert-CMSiteUpdateState -State $UpdateObj.State
            if ($StoppingStatus -notcontains $CMUpdateStatus) {
                if ($Detailed) {
                    Write-ToLogOrTerminal @LogParam -Severity Info -Message "times checked: $($CheckCount)"
                    Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed
                }
                else {
                    Write-CMSiteUpdateStatus -UpdateObj $UpdateObj
                }
                if ($CheckCount -eq 40) {
                    Write-ToLogOrTerminal @LogParam -Severity Info -Message "no state change detected for longer than $(((Get-Date) - $UpdateCheckStart).Minutes) minutes, restarting $($ServiceName) service"
                    Restart-Service -Name $ServiceName -Force -Verbose:$false
                }
            }
            else {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message "prereq checks for $($UpdateName) finished - current status: $($CMUpdateStatus)"
            }

            if ($CheckCount -ge 150) {
                throw "prereq checks took to long, check manually"
            }
            Start-Sleep -Seconds 15
        }
        while ($StoppingStatus -notcontains $CMUpdateStatus)
        
        if ($CMUpdateStatus -eq "INSTALL_IN_PROGRESS") {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "installation was successfully initated for $($UpdateName), for more details, review the CMUpdate.log - current status: $($CMUpdateStatus)"
        }
        elseif ($CMUpdateStatus -eq "PREREQ_ERROR") {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "prerequisite checks found some errors, please check manually - current status: $($CMUpdateStatus)"
        }
        elseif ($CMUpdateStatus -eq "PREREQ_WARNING") {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "prerequisite checks found some warnings, please check manually - current status: $($CMUpdateStatus)"
        }
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}

function Confirm-CMSiteUpdatePackageInstallation {
    <#
        .DESCRIPTION
        checks the install status of a running configmgr update
 
        .Parameter UpdateName
        name of the configmgr site update
 
        .Parameter Detailed
        turns on more detailed output of the current status
 
        .PARAMETER LogFileName
        name of the log file
 
        .PARAMETER LogFileFolderPath
        path of the folder where to put the log file
 
        .PARAMETER Terminal
        writes to Terminal instead of log file
 
        .EXAMPLE
        Confirm-CMSiteUpdatePackageInstallation -UpdateName $UpdateToInstall.Name
 
        .NOTES
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $UpdateName,
        
        [Parameter(Mandatory = $false)]
        [switch]
        $Detailed,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileName,

        [Parameter(Mandatory = $false)]
        [string]
        $LogFileFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]
        $Terminal
    )

    $ErrorActionPreference = 'Stop'
    $LogParam = Confirm-LogFileParameters -LogFileName $LogFileName -LogFileFolderPath $LogFileFolderPath -Terminal $Terminal

    $StoppingStatus = "INSTALL_SUCCESS", "INSTALL_FAILED"

    try {
        Write-ToLogOrTerminal @LogParam -Severity Info -Message "verifying installation of update $($UpdateName) is finished"
        do {
            if ($Detailed) {
                Write-ToLogOrTerminal @LogParam -Severity Info -Message "---"
            }
            $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName
            $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State
            if ($Detailed) {
                Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed
            }
            else {
                Write-CMSiteUpdateStatus -UpdateObj $UpdateObj
            }
            Start-Sleep -Seconds 15
        }
        while ($StoppingStatus -notcontains $StatusMessage)
    
        $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName
        $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State
        if ($StatusMessage -eq "INSTALL_SUCCESS") {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "installation of update $($UpdateName) is finished, post install steps are not finished yet."
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "admin console updates may be required"
    
            # do {
            # if($Detailed) {
            # Write-ToLogOrTerminal @LogParam -Severity Info -Message "---"
            # }
            # $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName
            # $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State
            # Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed
            # Start-Sleep -Seconds 15
            # }
            # while ($true)
            # Write-ToLogOrTerminal @LogParam -Severity Info -Message "post install of configmgr site update $($UpdateName) is finished"
        }
        else {
            Write-ToLogOrTerminal @LogParam -Severity Info -Message "current status $($StatusMessage)"
            throw "something went wrong - please check the logs"
        }
    }
    catch {
        Write-ToLogOrTerminal @LogParam -Severity Error -Message $PSItem.Exception.Message
        throw $PSItem.Exception.Message
    }
}