Deploy-DotNet48.psm1

function Get-RequierdAssets {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [object[]]$assets,
        [Parameter(Mandatory = $true)]
        [string]
        $share
    )
    $ErrorActionPreference = 'stop'
    Write-Verbose "checking required assets"
    [System.IO.Directory]::SetCurrentDirectory($share)
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    foreach ($asset in $assets) {
        Write-Verbose "processing $($asset.name)"
        $dl = ($share) + (Split-Path -Path $asset.URL -Leaf)
        if (!(Test-Path -Path $asset.fullpath)) {
            if ($asset.method -eq 'webclient') {
                (New-Object System.Net.WebClient).DownloadFile($asset.URL, $dl)
            }
            else {
                if ($asset.method -eq 'Start-BitsTransfer') {
                    $param = @{
                        'source'      = $asset.URL
                        'destination' = $dl
                    }
                }
                if ($asset.method -eq 'Invoke-WebRequest') {
                    $param = @{
                        'uri'     = $asset.URL
                        'OutFile' = $dl
                    }
                }
                & $asset.method @param
            }
            # TODO error handling
            $ext = [io.path]::GetExtension($dl)
            $ext = $ext.replace('.', '')
            if ($ext -eq 'zip') {
                Expand-Archive -Path $dl -DestinationPath $share -Force
                Remove-Item $dl -ErrorAction SilentlyContinue
            }
        }
        if ($null -ne $asset.cmd) {
            New-Item -Path $asset.scriptfullpath -Value $asset.cmd -Force | Out-Null
        }
    }
} #function Get-RequiredAssets
function Get-PatchState {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('Hostname', 'CN')]
        [string[]]
        $computername,
        [Parameter(Mandatory = $true)]
        [object[]]$assets,
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [pscredential]
        $Credential
    )
    foreach ($computer in $computername) {
        Write-Verbose "processing $computer"
        $params = @{
            'computername'   = $computer
            'Authentication' = 'CredSSP'
            'Credential'     = $Credential
            'ErrorAction'    = 'Stop'
        }
        try {
            $session = New-PSSession @params
        }
        catch {
            Write-Error "Failed to connect to $computer"
            return;
        }
        $os = Invoke-Command -Session $session -ScriptBlock { Get-WmiObject -Class Win32_OperatingSystem }
        $arch = $os.osarchitecture
        $os = $os | Select-Object -ExpandProperty version
        $ver = $os.IndexOf('.') + 2
        $os = $os.remove($ver)
        $arch = $arch.remove(2)
        $obj = [PSCustomObject]@{
        }
        # check asset installtion required environment, OS and Arch.
        foreach ($asset in $assets) {
            if (($os -like $asset.os) -and ($arch -like $asset.arch) -and ($null -ne $asset.pass)) {
                $result = Invoke-Expression $asset.pass
            }
            elseif (($os -like $asset.os) -and ($arch -like $asset.arch) -and ($null -eq $asset.pass)) {
                $result = 'false'
            }
            else {
                $result = 'NA'
            }
            $obj | Add-Member -Name $asset.pkg -MemberType NoteProperty -Value $result
        }
        Write-Output $obj
        Remove-PSSession $session
    }
}

function Deploy-Patch {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Enter a target computer name to deploy")]
        [Alias('Hostname', 'CN')]
        [string[]]$computername,
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [object]$asset,
        [Parameter(Mandatory = $true)]
        [string]$share,
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [pscredential]
        $Credential,
        [Parameter(Mandatory = $false)]
        [int]
        [ValidateRange(30, 1000)]
        $reboot_timeout
    )
    foreach ($computer in $computername) {
        Write-Verbose "processing $computer"
        $params = @{
            'computername'   = $computer
            'Authentication' = 'CredSSP'
            'Credential'     = $Credential
            'ErrorAction'    = 'Stop'
        }
        try {
            $session = New-PSSession @params
        }
        catch {
            Write-Error "Failed to connect to $computer"
            return;
        }
        Write-Verbose "installing $($asset.name)"
        #evaluate deployment method
        if ($null -ne $asset.pre_process) {
            $Script = [scriptblock]::Create($asset.pre_process)
            Invoke-Command -Session $session -ArgumentList $asset.pre_process -ScriptBlock $Script
        }
        if ($null -ne $asset.script) {
            $Script = [scriptblock]::Create($asset.script)
            Invoke-Command -Session $session -ScriptBlock $Script
        }
        elseif ($null -ne $asset.cmd) {
            Invoke-Command -Session $session -ArgumentList $asset.scriptfullpath -ScriptBlock { & $args[0] }
        }
        else {
            Invoke-Command -Session $session -ArgumentList $asset.fullpath -ScriptBlock { Start-Process $args[0] }
        }
        $ExitCode = Invoke-Command -Session $session -ScriptBlock { $LASTEXITCODE }
        if ($null -ne $asset.post_process) {
            $Script = [scriptblock]::Create($asset.post_process)
            Invoke-Command -Session $session -ArgumentList $asset.post_process -ScriptBlock $Script
        }
        $rebootState = (Get-RebootState -credential $Credential -ComputerName $Computer).IsRebootPending
        if (($rebootState -eq $true) -and ($PSBoundParameters.ContainsKey('reboot_timeout'))) {
            $params = @{
                'computername' = $computer
                'Force'        = $true
                'Wait'         = $true
                'For'          = 'WinRM'
                'timeout'      = $reboot_timeout
                'Credential'   = $Credential
            }
            Write-Verbose "Rebooting $computer"
            Restart-Computer @params
            $rebootState = 'rebooted'
        }
        $obj = [PSCustomObject]@{
            'Computername' = $computer
            'Asset'        = $asset
            'Status'       = $ExitCode
            'RebootState'  = $rebootState
        }
        Write-Output $obj
        Remove-PSSession $session
    } #foreach computer
}
function Resolve-UNCProperPath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$UNC,
        [switch]$test
    )
    #resolve mount point to full path
    if (($UNC).EndsWith(':')) {
        $UNC = ($UNC).Substring(0, 1)
        $unc = (Get-PSDrive -Name $UNC).Root
    } else {
        while ($UNC -notmatch '\\$') {
            $UNC += '\'
        }
        while ($UNC -notmatch '^\\\\') {
            $UNC = '\' + $UNC
        }
    }
    while (!(Test-Path -Path $UNC) -and ($PSBoundParameters.ContainsKey('test'))) {
        Write-Error "invalid share"
        $UNC = Read-Host "enter valid share"
        $UNC = Resolve-UNCProperPath -UNC $UNC -test
    }
    return $UNC
} #function
function Copy-Property ($From, $To) {
    foreach ($p in Get-Member -In $From -MemberType NoteProperty) {
        Add-Member -In $To -MemberType NoteProperty -Name $p.Name -Value $From.$($p.Name) -Force
    }
}
function Get-RebootState {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("CN", "Computer")]
        [String[]]
        $ComputerName = $env:COMPUTERNAME,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        [Parameter()]
        [Switch]
        $Detailed,

        [Parameter()]
        [Switch]
        $SkipConfigurationManagerClientCheck,

        [Parameter()]
        [Switch]
        $SkipPendingFileRenameOperationsCheck
    )

    process {
        foreach ($computer in $ComputerName) {
            $invokeWmiMethodParameters = @{
                Namespace    = 'root/default'
                Class        = 'StdRegProv'
                Name         = 'EnumKey'
                ComputerName = $computer
                ErrorAction  = 'silentlycontinue'
            }

            $hklm = [UInt32] "0x80000002"

            if ($PSBoundParameters.ContainsKey('Credential')) {
                $invokeWmiMethodParameters.Credential = $Credential
            }

            ## Query the Component Based Servicing Reg Key
            $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\')
            $registryComponentBasedServicing = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootPending'

            ## Query WUAU from the registry
            $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\')
            $registryWindowsUpdateAutoUpdate = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootRequired'

            ## Query JoinDomain key from the registry - These keys are present if pending a reboot from a domain join operation
            $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Services\Netlogon')
            $registryNetlogon = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames
            $pendingDomainJoin = ($registryNetlogon -contains 'JoinDomain') -or ($registryNetlogon -contains 'AvoidSpnSet')

            ## Query ComputerName and ActiveComputerName from the registry and setting the MethodName to GetMultiStringValue
            $invokeWmiMethodParameters.Name = 'GetMultiStringValue'
            $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\', 'ComputerName')
            $registryActiveComputerName = Invoke-WmiMethod @invokeWmiMethodParameters

            $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\', 'ComputerName')
            $registryComputerName = Invoke-WmiMethod @invokeWmiMethodParameters

            $pendingComputerRename = $registryActiveComputerName -ne $registryComputerName -or $pendingDomainJoin

            ## Query PendingFileRenameOperations from the registry
            if (-not $PSBoundParameters.ContainsKey('SkipPendingFileRenameOperationsCheck')) {
                $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\Session Manager\', 'PendingFileRenameOperations')
                $registryPendingFileRenameOperations = (Invoke-WmiMethod @invokeWmiMethodParameters).sValue
                $registryPendingFileRenameOperationsBool = [bool]$registryPendingFileRenameOperations
            }

            ## Query ClientSDK for pending reboot status, unless SkipConfigurationManagerClientCheck is present
            if (-not $PSBoundParameters.ContainsKey('SkipConfigurationManagerClientCheck')) {
                $invokeWmiMethodParameters.NameSpace = 'ROOT\ccm\ClientSDK'
                $invokeWmiMethodParameters.Class = 'CCM_ClientUtilities'
                $invokeWmiMethodParameters.Name = 'DetermineifRebootPending'
                $invokeWmiMethodParameters.Remove('ArgumentList')

                try {
                    $sccmClientSDK = Invoke-WmiMethod @invokeWmiMethodParameters
                    $systemCenterConfigManager = $sccmClientSDK.ReturnValue -eq 0 -and ($sccmClientSDK.IsHardRebootPending -or $sccmClientSDK.RebootPending)
                }
                catch {
                    $systemCenterConfigManager = $null
                    #Write-Warning -Message ($script:localizedData.invokeWmiClientSDKError -f $computer)
                }
            }

            $isRebootPending = $registryComponentBasedServicing -or `
                #$pendingComputerRename -or `
                #$pendingDomainJoin -or `
            $registryPendingFileRenameOperationsBool -or `
                $systemCenterConfigManager -or `
                $registryWindowsUpdateAutoUpdate

            if ($PSBoundParameters.ContainsKey('Detailed')) {
                [PSCustomObject]@{
                    ComputerName                     = $computer
                    ComponentBasedServicing          = $registryComponentBasedServicing
                    #PendingComputerRenameDomainJoin = $pendingComputerRename
                    PendingFileRenameOperations      = $registryPendingFileRenameOperationsBool
                    PendingFileRenameOperationsValue = $registryPendingFileRenameOperations
                    SystemCenterConfigManager        = $systemCenterConfigManager
                    WindowsUpdateAutoUpdate          = $registryWindowsUpdateAutoUpdate
                    IsRebootPending                  = $isRebootPending
                }
            }
            else {
                [PSCustomObject]@{
                    ComputerName    = $computer
                    IsRebootPending = $isRebootPending
                }
            }
        }
    }
} #function Get-RebootState