functions/private.ps1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
param()

#there are private, non-exported functions

Function _SleepProgress {
    [cmdletBinding()]
    Param(
        [int]$Minutes = 1
    )

    $Status = 'The lab will continue to run if you cancel. You can validate the lab later by running Run-Pester'
    $Seconds = $Minutes * 60
    $i = 0
    $TS = New-TimeSpan -Minutes $minutes
    do {
        [string]$Activity = "Waiting $minutes minute for lab configuration to merge or press Ctrl+C to cancel waiting."
        $i++
        $TS = $TS.subtract('0:0:1')

        Write-Progress -Activity $Activity -Status $status -CurrentOperation $TS
        Start-Sleep -Seconds 1
    } until ($i -ge $Seconds)

    Write-Progress -Activity $Activity -Completed
}
Function _PesterCheck {
    [CmdletBinding(SupportsShouldProcess)]
    Param()

    #1/31/2024 Revised to check for the latest version of Pester
    $currentPester = (Get-Module Pester -ListAvailable)[0]
    #Get-Module -FullyQualifiedName @{ModuleName = "Pester"; ModuleVersion = "$pesterVersion"} -ListAvailable
    if ($currentPester.Version -eq '3.4.0') {
        Write-Warning 'Pester v3.4.0 is installed and has never been updated. Installing the latest version of Pester'
        Install-Module -Name Pester -Force -SkipPublisherCheck
    }
    elseif ($currentPester.Version -lt $PesterVersion) {
        Write-Warning 'Pester v5.0.0 or later is required. Updating to the latest version of Pester'
        Update-Module -Name Pester
    }
    else {
        Write-Host 'Pester verified' -ForegroundColor green
    }

    #Import-Module -name Pester -RequiredVersion $PesterVersion -Force -Global
    Import-Module -Name Pester -Force -Global
}

Function _LabilityCheck {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$RequiredVersion,
        [Switch]$SkipPublisherCheck
    )

    $LabilityMod = Get-Module -Name Lability -ListAvailable | Sort-Object Version -Descending

    $PSBoundParameters.Add('Name', 'Lability')
    $PSBoundParameters.Add('Force', $True)
    $PSBoundParameters.Add('ErrorAction', 'Stop')

    $PSBoundParameters | Out-String | Write-Verbose
    if (-Not $LabilityMod) {
        Write-Host -ForegroundColor Cyan "Installing Lability module version $requiredVersion"
        Install-Module @PSBoundParameters #-Name Lability -RequiredVersion $requiredVersion -Force
    }
    elseif ($LabilityMod[0].Version.ToString() -eq $requiredVersion) {
        Write-Host "Version $requiredVersion of Lability is already installed" -ForegroundColor Cyan
    }
    elseif ($LabilityMod[0].Version.ToString() -ne $requiredVersion) {
        Write-Host -ForegroundColor Cyan "Updating Lability Module to version $RequiredVersion"
        #remove the currently loaded version
        Remove-Module -Name Lability -ErrorAction SilentlyContinue
        try {
            if ($SkipPublisherCheck) {
                Write-Verbose 'Skipping publisher check and calling Install-Package'
                Install-Package @PSBoundParameters
            }
            else {
                Write-Verbose 'Calling Update-Module'
                [void]($PSBoundParameters.remove('SkipPublisherCheck'))
                Update-Module @PSBoundParameters
            }
        }
        Catch {
            Write-Warning "Failed to update to the current version of Lability. If the error message is about a certificate mismatch, re-run this command and use the -SkipPublisherCHeck parameter. `n$($_.exception.message)"
        }
    }
} #end function

Function Invoke-WUUpdate {
    [CmdletBinding(DefaultParameterSetName = 'computer')]

    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'computer')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Computername,
        [Parameter(Position = 0, ParameterSetName = 'VM')]
        [string[]]$VMName,
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,
        [Switch]$AsJob
    )

    Begin {
        Write-Verbose "[BEGIN ] Starting: $($MyInvocation.MyCommand)"

        $all = @()

        #this is a nested function to deploy remotely
        Function WUUpdate {
            [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Computer')]
            Param(
                [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Computer')]
                [ValidateNotNullOrEmpty()]
                [string[]]$Computername = $env:COMPUTERNAME

            )
            Begin {
                Write-Verbose "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
                $ns = 'ROOT/Microsoft/Windows/WindowsUpdate'
            } #begin

            Process {

                #test if using the new Class
                Try {
                    [void](Get-CimClass -Namespace $ns -ClassName 'MSFT_WUOperations' -ErrorAction Stop)
                    $class = 'MSFT_WUOperations'
                    $scanArgs = @{SearchCriteria = 'IsInstalled=0' }

                    #always scan even if the function is run with -WhatIf
                    Write-Host "[$(Get-Date)] Scanning for updates to install on $($env:computername)" -ForegroundColor Cyan
                    $scan = Invoke-CimMethod -Namespace $ns -ClassName $class -MethodName 'ScanForUpdates' -Arguments $scanArgs -WhatIf:$false -ErrorAction Stop

                    Write-Host "[$(Get-Date)] Found $($scan.updates.count) updates to install on $($env:computername)" -ForegroundColor Cyan
                    if ($scan.Updates.count -gt 0) {
                        if ($PSCmdlet.ShouldProcess("$($scan.updates.count) updates", 'Install Updates' )) {
                            [void](Invoke-CimMethod -Namespace $ns -ClassName MSFT_WUOperations -MethodName InstallUpdates -Arguments @{Updates = $scan.updates })
                        }
                    }
                } #try
                Catch {
                    #uncomment for debugging and troubleshooting
                    #Write-Host "Failed to find MSFT_WUOperations on $env:computername" -ForegroundColor yellow
                    $class = 'MSFT_WUOperationsSession'
                    $scanArgs = @{OnlineScan = $True; SearchCriteria = 'IsInstalled=0' }
                    $ci = New-CimInstance -Namespace $ns -ClassName $class -WhatIf:$False

                    Write-Host "[$(Get-Date)] Scanning for updates to install on $($env:computername)" -ForegroundColor Cyan
                    $scan = $ci | Invoke-CimMethod -MethodName 'ScanForUpdates' -Arguments $scanArgs -WhatIf:$False

                    Write-Host "[$(Get-Date)] Found $($scan.updates.count) updates to install on $($env:computername)" -ForegroundColor Cyan
                    if ($scan.Updates.count -gt 0) {
                        if ($PSCmdlet.ShouldProcess("$($scan.updates.count) updates", 'Apply Updates' )) {
                            [void]($ci | Invoke-CimMethod -MethodName applyApplicableUpdates )
                        }
                    }
                } #catch

                if ($scan.updates.count -gt 0) {
                    Write-Host "[$(Get-Date)] Update process complete on $env:computername" -ForegroundColor Cyan
                }

                #check for reboot

                $r = Invoke-CimMethod -Namespace $ns -ClassName 'MSFT_WUSettings' -MethodName 'IsPendingReboot'
                if ($r.PendingReboot) {
                    Write-Warning "$($env:computername) requires a reboot"
                }

                if ($ci) {
                    Remove-Variable ci
                }

            } #process

            End {
                Write-Verbose "[END ] Ending: $($MyInvocation.MyCommand)"
            } #end
        } #end nested function

        #get the contents of the nested function
        $fun = ${function:WUUpdate}

        $icmParams = @{
            HideComputername = $True
            Session          = $null
            Scriptblock      = { WUUpdate }
        }

        if ($AsJob) {
            $icmparams.AsJob = $True
            $icmParams.JobName = 'WUUpdate'
        }

    } #begin

    Process {

        if ($PSBoundParameters.ContainsKey('AsJob')) {
            [void]$PSBoundParameters.remove('AsJob')
        }

        #Write-Verbose ($PSBoundParameters | Out-String)

        Try {
            Write-Verbose '[PROCESS] Creating PSSessions'
            $sess = New-PSSession @PSBoundParameters -ErrorAction stop
            Write-Verbose '[PROCESS] Copy the function to the remote computer'
            [void](Invoke-Command -ScriptBlock { New-Item -Path Function:WUUpdate -Value $using:fun -Force } -Session $sess)

            $icmParams.Session = $sess
        }
        Catch {
            Write-Warning "Failed to create session to $Computer. $($_.exception.message)"
        }
        Write-Verbose '[PROCESS] Run the remote function'
        $r = Invoke-Command @icmParams
        if ($AsJob) {
            $r
        }
        else {
            $r | Select-Object -Property * -ExcludeProperty RunspaceID
            Write-Verbose '[PROCESS] Cleaning up sessions'
            $sess | Remove-PSSession
        }

    } #process
    End {
        Write-Verbose "[END ] Ending: $($MyInvocation.MyCommand)"
    } #end

}

Function Test-IsAdministrator {
    $user = [Security.Principal.WindowsIdentity]::GetCurrent();
    (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}