Deploy-DotNet48.psm1


function Deploy-DotNet48 {
    <#
    #>

    
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Enter a computer name to deploy")]
        [Alias('Hostname')]
        [string[]]$computername,
        [string]$share = '\\db\vol1\',
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [pscredential]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter(Mandatory = $false)]
        [switch]
        $force_reboot,
        [Parameter(Mandatory = $false)]
        [String]
        $CustomerQuery,
        [Parameter(Mandatory = $false)]
        [int]
        $reboot_timeout = '500'
    )
    begin {
        Write-Verbose "Execution Metadata:"
        Write-Verbose "User = $($env:userdomain)\$($env:USERNAME)"
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $IsAdmin = [System.Security.Principal.WindowsPrincipal]::new($id).IsInRole('administrators')
        Write-Verbose "Is Admin = $IsAdmin"
        Write-Verbose "Computername = $env:COMPUTERNAME"
        Write-Verbose "OS = $((Get-CimInstance Win32_Operatingsystem).Caption)"
        Write-Verbose "Host = $($host.Name)"
        Write-Verbose "PSVersion = $($PSVersionTable.PSVersion)"
        Write-Verbose "Runtime = $(Get-Date)"
        Write-Verbose "[$((get-date).TimeOfDay.ToString()) BEGIN ] Starting: $($MyInvocation.Line)"
        $params = @{
            'Share'         = $share
            'CustomerQuery' = $CustomerQuery
        }
        if ($PSBoundParameters.ContainsKey('verbose')) {
            $params += @{'verbose' = $true }
        }
        $assets = Get-RequierdAssets @params
    }#begin
    Process {
        $params = @{'assets' = $assets
            'credential'     = $Credential
            'force_reboot'   = $false
            'reboot_timeout' = $reboot_timeout
        }
        $params2 = @{}
        if ($PSBoundParameters.ContainsKey('force_reboot')) {
            $params.force_reboot = $true 
        }
        if ($PSBoundParameters.ContainsKey('verbose')) {
            $params += @{'verbose' = $true }
            $params2 += @{'verbose' = $true }
        }
        $computername | ForEach-Object -process {
            $_ | Get-PatchState @using:params2 | Deploy-Patch @using:params
        }
    }#process

} #function
function Get-RequierdAssets {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]
        $share,
        [Parameter(Mandatory = $false)]
        [String]
        $CustomerQuery
    )
    $ErrorActionPreference = 'stop'
    Write-Verbose "checking required Assets"
    $share = Resolve-UNCProperPath -UNC $share
    while (!(Test-Path -Path $share)) {
        $share = Read-Host "share invalid. enter valid share"
        $share = Resolve-UNCProperPath -UNC $share
    }
    $share += 'apps\net\'
    $PSexec = 'PStools\psexec.exe'
    $ndp = 'ndp48-x86-x64-allos-enu.exe'
    $SyncroInstaller = 'syncro-installer.exe'
    $Cert = 'rootsupd.exe'
    $WMF_win81 = 'Win8.1AndW2K12R2-KB3191564-x64.msu'
    $WMF_win7x86 = 'Win7-KB3191566-x86.msu'
    $WMF_win7x64 = 'Win7AndW2K8R2-KB3191566-x64.msu'
    $UCRT_win7x86 = 'Windows6.1-KB3118401-x86.msu'
    $UCRT_win7x64 = 'Windows6.1-KB3118401-x64.msu'
    $UCRT_win81 = 'Windows8.1-KB3118401-x64.msu'
    $UCRT_directory = 'WindowsUCRT\'
    $PS7_x86 = 'PowerShell-7.1.0-win-x86.msi'
    $PS7_x64 = 'PowerShell-7.1.0-win-x64.msi'

    $assets = [pscustomobject]@{
        'ndpPath'             = $share + $ndp
        'SyncroInstallerPath' = $share + $SyncroInstaller
        'SyncroID'            = $share + 'syncroid.txt'
        'psexecPath'          = $share + $PSexec
        'ndpcmd'              = $share + 'ndp.bat'
        'certPath'            = $share + $Cert
        'WMF_win81path'       = $share + $WMF_win81
        'WMF_win7x86path'     = $share + $WMF_win7x86
        'WMF_win7x64path'     = $share + $WMF_win7x64
        'UCRT_path'           = $share + $UCRT_directory
        'UCRT_win7x64path'    = $share + $UCRT_directory + $UCRT_win7x64
        'UCRT_win7x86path'    = $share + $UCRT_directory + $UCRT_win7x86
        'UCRT_win81path'      = $share + $UCRT_directory + $UCRT_win81
        'PS7_64path'          = $share + $PS7_x64
        'PS7_86path'          = $share + $PS7_x86
    }
    [System.IO.Directory]::SetCurrentDirectory($pwd)
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    Write-Verbose "Checking for $($assets.psexecPath)"
    if ((Test-Path -PathType Leaf $assets.psexecPath) -eq $false) {
        New-Item -ItemType Directory -Path $share -name 'PStools' -Force | Out-Null
        $acl = Get-Acl -Path $share
        $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule('everyone', 'deletesubdirectoriesandfiles', 'deny')
        $acl.SetAccessRule($AccessRule)
        $acl | Set-Acl -path $share
        Write-Verbose "Downloading PSTools"
        $Url = 'https://download.sysinternals.com/files/PSTools.zip'
        Start-BitsTransfer -Source $Url
        Write-Verbose "Extracting PSexec from archive"
        Expand-Archive -Path .\PSTools.zip -DestinationPath $($share + "PStools") -Force
    }
    Remove-Item .\pstools.zip -ErrorAction SilentlyContinue
    Write-Verbose "Checking for $($assets.ndpPath)"
    if ((Test-Path -PathType Leaf $assets.ndpPath) -eq $false) {
        Write-Verbose "Creating folder $($share)"
        New-Item -ItemType Directory -Path $share -Force | Out-Null
        $Url = 'https://download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/0fd66638cde16859462a6243a4629a50/ndp48-x86-x64-allos-enu.exe'
        Write-Verbose "Downloading $($ndp)"
        Start-BitsTransfer -Source $Url -Destination $assets.ndpPath
    }
    Write-Verbose "Checking for $($assets.ndpcmd)"
    if ((Test-Path -PathType Leaf $assets.ndpcmd) -eq $false) {
        #since powershell 2.0 does not have the --% stop parsing, will need to copy installer files to target,
        #echo to a bat file a list of required commands to execute local install
        Write-Verbose "Creating script ndp.bat"
        $cmd = "$($assets.psexecPath) -acceptEULA -s `"c:\temp\$($ndp)`" /q /norestart /log C:\Temp\NetFx48.txt"
        New-Item -Path $share -Name ndp.bat -Value $cmd -Force | Out-Null
    }
    Write-Verbose "Checking for $($assets.SyncroInstallerPath)"
    if (!(Test-Path -PathType Leaf $assets.syncroInstallerPath) -or (!(Test-Path -PathType Leaf $assets.SyncroID))) {
        Write-Verbose "Downloading Syncro Installer"
        Get-SyncroRMM($CustomerQuery)
        while (!(Test-Path $assets.SyncroID)) {
            $CustomerQuery = Read-Host "enter Customer to lookup"
            Get-SyncroRMM($CustomerQuery) -ErrorAction inquire
        }
        $acl = Get-Acl -Path $assets.SyncroInstallerPath
        $accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule('everyone', 'write,delete,changepermissions,takeownership', 'deny')
        $acl.SetAccessRule($AccessRule)
        $acl | Set-Acl -path $assets.SyncroInstallerPath
    }
    Write-Verbose "Checking for $($assets.certPath)"
    if ((Test-Path -PathType Leaf $assets.certPath) -eq $false) {
        Write-Verbose "Downloading $($Cert)"
        $Url = 'http://media.kaspersky.com/utilities/CorporateUtilities/rootsupd.zip'
        Invoke-WebRequest -Uri $url -OutFile '.\rootsupd.zip'
        Write-Verbose "Extracting $($cert) from archive"
        Expand-Archive -Path .\rootsupd.zip -DestinationPath $share
    }
    Remove-Item .\rootsupd.zip -ErrorAction SilentlyContinue
    Write-Verbose "Checking for WMF"
    if ((Test-Path -PathType Leaf $assets.WMF_win7x64path) -eq $false) {
        Write-Verbose "Downloading WMF 5.1 packages"
        $Url = 'https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7AndW2K8R2-KB3191566-x64.zip'
        Start-BitsTransfer -Source $url -Destination '.\Win7AndW2K8R2-KB3191566-x64.zip'
        Write-Verbose "Extracting WMF 5.1 Win 7 x64"
        Expand-Archive -Path .\Win7AndW2K8R2-KB3191566-x64.zip -DestinationPath $share -Force
        $Url = 'https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7-KB3191566-x86.zip'
        Start-BitsTransfer -Source $url -Destination '.\Win7-KB3191566-x86.zip'
        Write-Verbose "Extracting WMF 5.1 Win 7 x86"
        Expand-Archive -Path .\Win7-KB3191566-x86.zip -DestinationPath $share -Force
        $Url = 'https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1AndW2K12R2-KB3191564-x64.msu'
        Start-BitsTransfer -Source $url -Destination $assets.WMF_win81path
    }
    Remove-Item .\Win7AndW2K8R2-KB3191566-x64.zip, .\Win7-KB3191566-x86.zip -ErrorAction SilentlyContinue
    Write-Verbose "Checking for $($assets.UCRT_path)"
    if ((Test-Path -PathType Container $assets.UCRT_path) -eq $false) {
        Write-Verbose "Downloading Universal C Runtime"
        $Url = 'https://download.microsoft.com/download/3/1/1/311C06C1-F162-405C-B538-D9DC3A4007D1/WindowsUCRT.zip'
        Start-BitsTransfer -Source $url -Destination '.\WindowsUCRT.zip'
        Write-Verbose "Extracting Universal C runtime from archive"
        New-Item -ItemType Directory -Name "WindowsUCRT" -Force -Path $share | Out-Null
        Expand-Archive -Path .\WindowsUCRT.zip -DestinationPath $assets.UCRT_path -Force
    }
    Remove-Item .\WindowsUCRT.zip -ErrorAction SilentlyContinue
    Write-Verbose "Checking for $($PS7_x64)"
    if ((Test-Path -PathType Leaf $assets.PS7_64path) -eq $false) {
        Write-Verbose "Downloading PowerShell 7 x64"
        $Url = 'https://github.com/PowerShell/PowerShell/releases/download/v7.1.0/PowerShell-7.1.0-win-x64.msi'
        Invoke-WebRequest -Uri $Url -OutFile $assets.PS7_64path
    }
    Write-Verbose "Checking for $($PS7_x86)"
    if ((Test-Path -PathType Leaf $assets.PS7_86path) -eq $false) {
        Write-Verbose "Downloading PowerShell 7 x86"
        $Url = 'https://github.com/PowerShell/PowerShell/releases/download/v7.1.0/PowerShell-7.1.0-win-x86.msi'
        Invoke-WebRequest -Uri $url -OutFile $assets.PS7_86path
    }
    Write-Output $assets
} #function Get-RequiredAssets

function Get-PatchState {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $ComputerName
    )
    try {
        $session = New-PSSession -ComputerName $ComputerName -ErrorAction Stop
    }
    catch { return; }
    Write-Verbose "getting OS detailes for $ComputerName"
    $os = Invoke-Command -Session $session -ScriptBlock { Get-WmiObject -Class Win32_OperatingSystem }
    Write-Verbose "OS is $($os.caption) $($os.OSArchitecture)"
    Write-Verbose "Checking if .net 4.8 is installed on $ComputerName"
    $TargetNDPversion = 4.8
    $InstalledNDPversion = (Invoke-Command -Session $session -ScriptBlock { Get-ItemProperty -ErrorAction SilentlyContinue -path 'hklm:\SOFTWARE\Microsoft\net Framework setup\ndp\v4\full' -Name version }).version
    if ($InstalledNDPversion -ge $TargetNDPversion) {
        $installedNDP = $true
    }
    else {
        $installedNDP = $false
    }
    #Write-Host "NDP 4.8 is"(& { If ($installedNDP -eq $true) { "already" } Else { "not" } })"installed"
    Write-Verbose "Checking if WMF 5.1 is installed on $ComputerName"
    $TargetWMFversion = "5"
    $wmf = Invoke-Command -Session $session -ScriptBlock { ($PSVersionTable).PSVersion.Major }
    if ($wmf -ge $TargetWMFversion) {
        $installedWMF = $true
    }
    else {
        $installedWMF = $false
    }
    #Write-Host "WMF 5.1 is"(& { If ($installedWMF -eq $true) { "already" } Else { "not" } })"installed"
    Write-Verbose "Checking for Universal C runtime on $ComputerName"
    if (! ((Invoke-Command -Session $session -ScriptBlock { (((Get-Item -ErrorAction SilentlyContinue $env:windir\system32\ucrtbase_clr0400.dll).VersionInfo).ProductVersion) }) -ge 14.10) -or ((Invoke-Command -session $session -ScriptBlock { (Test-Path -Path $env:windir\system32\ucrtbase.dll) }) -eq $false)) {
        $installedUCRT = $false
    }
    else {
        $installedUCRT = $true
    }
    #Write-Host "Universal C Runtime is"(& { If ($installedUCRT -eq $true) { "already" } Else { "not" } })"installed"
    Write-Verbose "Checking for PowerShell 7"
    $installedPS = (Invoke-Command -Session $session -ScriptBlock { Get-WmiObject -Class Win32_Product | Where-Object -FilterScript { $_.name -like "powershell 7*" } })
    if ($null -eq $installedPS) {
        $installedPS = $false
    }
    else {
        $installedPS = $true
    }
    Write-Verbose "Checking For SyncroRMM Agent"
    $installedSyncro = (Invoke-Command -Session $session -ScriptBlock { Get-Item -ErrorAction SilentlyContinue -path 'HKLM:\SOFTWARE\Microsoft\windows\CurrentVersion\Uninstall\syncro' })
    if ($null -eq $installedSyncro) {
        $installedSyncro = $false
    }
    else {
        $installedSyncro = $true
    }
    #Write-Host "Syncro is"(& { If ($installedSyncro -eq $true) { "already" } Else { "not" } })"installed"
    $RebootPending = (Get-RebootState -ComputerName $ComputerName).IsRebootPending
    $obj = [PSCustomObject]@{
        'ComputerName'    = $ComputerName
        'OSVersion'       = $os.Caption
        'Arch'            = $os.OSArchitecture
        'installedNet'    = $installedNDP
        'installedWMF'    = $installedWMF
        'installedUCRT'   = $installedUCRT
        'Powershell7'     = $installedPS
        'installedSyncro' = $installedSyncro
        'RebootPending'   = $false
    }
    return $obj
    $session | Remove-PSSession
}
function Resolve-UNCProperPath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$UNC
    )
    while ($UNC -notmatch '\\$') {
        $UNC += '\'
    }
    while ($UNC -notmatch '^\\\\') {
        $UNC = '\' + $UNC
    }
    return $UNC
} #function

function Deploy-Patch {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Object[]]
        $io,
        [Parameter(Mandatory = $true)]
        [object]
        $assets,
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [pscredential]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter(Mandatory = $false)]
        [switch]
        $force_reboot,
        [Parameter(Mandatory = $false)]
        [int]
        $reboot_timeout
    )
    $PSDefaultParameterValues = (@{"invoke-command:Authentication" = "CredSSP"; "Invoke-Command:credential" = $credential; "New-PSSession:authentication" = "CredSSP"; "New-PSSession:credential" = $Credential })
    ForEach ($computer in $io) {
        try {
            $session = New-PSSession -ComputerName $Computer.Computername -ErrorAction Stop
        }
        catch {
            Write-Error "failed to connect to $($compter.computername)"
            return; 
        }
        if ($computer.installedNet -eq $false) {
            Write-Verbose "installing Certificates"
            $jobname = "cert_" + $computer.Computername
            Invoke-Command -Session $session -ArgumentList $assets.certPath -ScriptBlock { Start-Process -FilePath $args[0] -wait } -AsJob -JobName $jobname
            Wait-Job -Name $jobname
            Write-Verbose "installing NDP"
            $jobname = "NDP_" + $computer.Computername
            Invoke-Command -Session $session -ArgumentList $assets.ndpPath -ScriptBlock { New-Item -Path C:\TEMP -ItemType Directory -Force | Out-Null; Copy-Item -Path $args[0] -Destination c:\temp\ }
            Invoke-Command -Session $session -ArgumentList $assets.ndpcmd -ScriptBlock { & $args[0] } -AsJob -JobName $jobname
            Wait-Job -Name $jobname
            $NDPinstallExitCode = Invoke-Command -Session $session -ScriptBlock { $LASTEXITCODE }
            $result = switch ($NDPinstallExitCode) {
                0 { $false } #Installation completed successfully
                1602 { $null } #The user canceled installation
                1602 { $null } #The user canceled installation
                1603 { $null } #A fatal error occurred during installation
                1641 { $true } #A restart is required to complete the installation. This message indicates success
                1726 { $false } #RPC Failed
                3010 { $true } #A restart is required to complete the installation. This message indicates success
                5100 { $null } #"The user's computer does not meet system requirements
                Default { $null }
            }
            if (($result -eq '0') -or ($result -eq '1641') -or ($result -eq '3010')) {
                $computer.installedNDP = $true
            }
        }
        Write-Verbose "checking for pending reboot"
        $computer.RebootPending = (Get-RebootState -ComputerName $computer.computername).IsRebootPending
        Write-Verbose "Reboot Pending: $($computer.RebootPending)"
        if ($computer.RebootPending -eq $true) {
            if ($force_reboot -eq $true) {
                $session | Remove-PSSession
                restart-computer -ComputerName $computer.computername -wait -for WinRM -timeout $reboot_timeout -delay 5 -force
                Write-Verbose "Waiting for $($computer.computername) to respond"
                $session = New-PSSession -ComputerName $Computer.Computername 
                $computer.RebootPending = $false
            }
        }
            if ($computer.installedWMF -eq $false) {
                if ($computer.OSVersion -like "*windows 7*") {
                    if ($computer.Arch -like "64*") {
                        $WMF = " Copy-Item -Path $($assets.WMF_win7x64path) -Destination C:\temp\
                    Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Win7AndW2K8R2-KB3191566-x64.msu /extract:C:\MSU\`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB2809215-x64.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB2872035-x64.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB2872047-x64.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3033929-x64.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3191566-x64.cab /NoRestart`" -Wait -PassThru
                    Remove-Item C:\MSU -Recurse -Force "

                    }
                    else {
                        $WMF = "Copy-Item -Path $($assets.WMF_win7x86path) -Destination c:\temp\
                    Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Win7-KB3191566-x86.msu /extract:C:\MSU\`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB2872035-x86.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB2872047-x86.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3033929-x86.cab /NoRestart`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3191566-x86.cab /NoRestart`" -Wait -PassThru
                    Remove-Item C:\MSU -Recurse -Force "

                    }
                }
                elseif ($computer.OSVersion -like "*2012 R2*") {
                    $WMF = " Copy-Item -Path $($assets.WMF_win81path) -Destination c:\temp\
                Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Win8.1AndW2K12R2-KB3191564-x64.msu /extract:C:\MSU\`" -Wait -PassThru
                Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\WindowsBlue-KB3191564-x64.cab /NoRestart`" -Wait -PassThru
                Remove-Item C:\MSU -Recurse -Force "

                }
                $WMFScript = [scriptblock]::Create($WMF)
                Write-Verbose "installing WMF 5.1"
                $jobname = "WMF_" + $computer.ComputerName
                Invoke-Command -Session $session -ScriptBlock $WMFScript -AsJob -JobName $jobname
                Wait-Job -Name $jobname
            }
        }
        if (($computer.installedSyncro -eq $false) -and ($computer.RebootPending -eq $false)) {
            Write-Verbose "installing SyncroRMM"
            $jobname = "SyncroRMM_" + $computer.Computername
            $policy = switch -wildcard ($computer.OSVersion, $computer.computername) {
                *server* { '50508'; break }
                *office* { '50510' ; break }
                *register* { '50506' ; break }
            }
            if ($null -eq $policy) {
                $policy = '93772'
            }
            $params = "--customerid $(get-content $assets.syncroid) --console --policyid $($policy)"
            Invoke-Command -Session $session -ArgumentList $assets.syncroInstallerPath, $params -ScriptBlock { Start-Process $args[0] -ArgumentList $args[1] }# -AsJob -JobName $jobname
            #wait till service syncrolive starts, otherwise install will fail
            Write-Verbose "open session:"
            $end = '120'
            for ($i = 0; $i -lt $end; $i++) {
                $service = Invoke-Command -Session $session -ScriptBlock { Get-Service -Name SyncroLive -ErrorAction SilentlyContinue | Where-Object -FilterScript { $_.Status -eq 'running' } }
                if ($null -eq $service) {
                    Write-Verbose "$($end - $i) Waiting for SyncroLive service to start on $($computer.computername)"    
                    Start-Sleep 1
                }
            }
            #Wait-Job -Name $jobname
        if ($computer.installedUCRT -eq $false) {
            if ($computer.OSVersion -like "*windows 7*") {
                if ($computer.Arch -like "64*") {
                    #$ucrt = $assets.UCRT_win7x64path
                    $ucrt = "Copy-Item -Path $($assets.UCRT_win7x64path) -Destination c:\temp\
                    Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Windows6.1-KB3118401-x64.msu /extract:C:\MSU\`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3118401-x64.cab /NoRestart`" -Wait -PassThru
                    Remove-Item C:\MSU -Recurse -Force "

                }
                else {
                    #$ucrt = $assets.UCRT_win7x86path
                    $ucrt = "Copy-Item -Path $($assets.UCRT_win7x86path) -Destination c:\temp\
                    Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Windows6.1-KB3118401-x86.msu /extract:C:\MSU\`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows6.1-KB3118401-x86.cab /NoRestart`" -Wait -PassThru
                    Remove-Item C:\MSU -Recurse -Force "

                }
            }
            elseif ($computer.OSVersion -like "*2012 R2") {
                #$ucrt = $assets.UCRT_win81path
                $ucrt = "Copy-Item -Path $($assets.UCRT_win81path) -Destination c:\temp\
                    Start-Process 'wusa.exe' -ArgumentList `"c:\temp\Windows8.1-KB3118401-x64.msu /extract:C:\MSU\`" -Wait -PassThru
                    Start-Process dism.exe -ArgumentList `"/online /add-package /PackagePath:C:\MSU\Windows8.1-KB3118401-x64.cab /NoRestart`" -Wait -PassThru
                    Remove-Item C:\MSU -Recurse -Force "

            }
            Write-Verbose "installing Universal C Runtime"
            $jobname = "UCRT_" + $computer.ComputerName
            $UCRTScript = [scriptblock]::Create($UCRT)
            Invoke-Command -Session $session -ScriptBlock $UCRTScript -AsJob -JobName $jobname
            Wait-Job -Name $jobname
        }
        Write-Verbose "checking for pending reboot"
        $computer.RebootPending = (Get-RebootState -ComputerName $computer.computername).IsRebootPending
        Write-Verbose $computer.RebootPending
        if ($computer.RebootPending -eq $true) {
            if ($force_reboot -eq $true) {
                $session | Remove-PSSession
                restart-computer -ComputerName $computer.computername -wait -for WinRM -timeout $reboot_timeout -delay 5 -force
                $session = New-PSSession -ComputerName $Computer.Computername 
                $computer.RebootPending = $false
            }
        }
        
        if ($computer.Powershell7 -eq $false) {
            if ($computer.Arch -like "64*") {
                $PS7 = $assets.PS7_64path
            }
            else {
                $PS7 = $assets.PS7_86path
            }
            Write-Verbose "Installing Powershell 7"
            $arguments = "/package $PS7 /quiet ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1"
            Invoke-Command -Session $session -ArgumentList $arguments -ScriptBlock { Start-Process msiexec.exe -ArgumentList $args[0] -Wait } -ErrorAction SilentlyContinue
        }
        $computer
    } #foreach
} #function Deploy-Patch


function Get-SyncroRMM ($SyncroCustomerQuery) {
    $InstallerUrl = "https://rmm.syncromsp.com/dl/rs/MTY1MjI4NTItMTU3Mzg5NzEwMy00OTkzNy05Mzc3Mg=="
    $LookupToken = "mFjNAD8aBlF-eNk3HHumfw"
    $Subdomain = "poswithlogic"
    $ErrorActionPreference = "Stop"

    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null
    $wc = New-Object System.Net.WebClient
    Write-Output "Getting CustomerId"
    $CustomerSearchUrl = "https://$($Subdomain).syncromsp.com/api/syncro_device/customer_lookups/lookup?lookup_token=$($LookupToken)&query=$($SyncroCustomerQuery)"
    $SyncroCustomerMatches = $wc.DownloadString($CustomerSearchUrl)
    # Write-Output $SyncroCustomerMatches
    Write-Output "Parsing json mappings"
    $CustomerLookups = ConvertFrom-Json20($SyncroCustomerMatches)
    Write-Output $CustomerLookups

    #check for lookup error
    if ($SyncroCustomerMatches -Match "error") {
        Write-Host "Error: $($CustomerLookups.error)"
        return;
    }

    Write-Output "Looking for corresponding syncro customer"
    $syncroCustomerId = ""
    $SyncroCustomerMapping = $CustomerLookups.customer_lookups

    # if more than one customer found - list them and exit
    if ([int]$SyncroCustomerMapping.id.count -gt 1) {
        Write-Host "More than one customer found, please look at the results and refine your search to the correct customer:"
        ForEach ($mapping in $SyncroCustomerMapping) {
            ForEach ($group in $mapping) {
                Write-Host $group.firstname $group.lastname " | " $group.business_name " | " $group.id

            }
        }
        return
    }
    else {
        #If only one match is found, download and install
        if ($SyncroCustomerMatches -Match "id") {
            Write-Host "CustomerID found: $($CustomerLookups.id)"
            $syncroCustomerId = $CustomerLookups.id | out-file -filepath $assets.SyncroID;
        }
        else {
            #if no customer matches found, exit
            Write-Host "Corresponding Syncro customer not found"
            return
        }

    }
    Write-Output "Downloading syncro installer from $InstallerUrl"
    #$syncroInstallerPath = "$env:TEMP\$InstallerName"
    $wc.DownloadFile($InstallerUrl, $assets.SyncroInstallerPath)
} #Get-SyncroRMM
#this is just a function for reading JSON, no editing needed
function ConvertFrom-Json20([object] $item) {
    add-type -assembly system.web.extensions
    $ps_js = new-object system.web.script.serialization.javascriptSerializer
    #The comma operator is the array construction operator in PowerShell
    return , $ps_js.DeserializeObject($item)
} #convertFrom-Json20
# this function might error on PS 7.1.0
# https://github.com/PowerShell/PowerShell/issues/13195