CT-PS-Standard.psm1

<#
--------------------------------------------------------------------------------------------------
 
This is a standard module with a set of standard functions used across multiple scripts within CT.
 
Any changes to this module need to be published using the powershell script "Build_CT_Module.ps1"
 
--------------------------------------------------------------------------------------------------
HOW TO IMPORT INTO SCRIPT:
--------------------------------------------------------------------------------------------------
This module should be imported using the commands below (do not copy the asterix's, just whats between).
This will import this module AND initialise the script with all the standard features required by scripts,
including all the log files for each transaction
*****************
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-Module -Name CT-PS-Standard -Force -AllowClobber
Initialize-Script
*****************
 
However, if there is any corruption in Powershell on the machine, you may need to use the following scripting block.
In fact, this block may be preferred as it ensures all systems are updated before installing the module.
 
*****************
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
if ($null -eq (Get-PSRepository)) {
    Register-PSRepository -Default -Verbose:$VerbosePreference
}
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue | Out-Null
Remove-Module -Name CT-PS-Standard -Force -Verbose:$VerbosePreference -ErrorAction SilentlyContinue
Uninstall-Module -Name "CT-PS-Standard" -AllVersions -Force -ErrorAction SilentlyContinue -Verbose:$VerbosePreference
Install-Module -Name CT-PS-Standard -Force -AllowClobber -Verbose:$VerbosePreference | Import-Module
Initialize-Script
*****************
 
--------------------------------------------------------------------------------------------------
 
 
--------------------------------------------------------------------------------------------------
LOG FILES
--------------------------------------------------------------------------------------------------
There are four log files initialised by this module that can be used for output.
You can write to each of these logs accordingly.
 
$Output_Log: This is the standard console output.
$Transcript_log: This is where the transcript is written to. You will need to start and stop the transcript inside your script by using the command "Start-Transcript -Path $Transcript_log -append | Out-Null"
$API_log: This is where the output from API posts should be sent.
$Install_log: This is where output from MSIEXEC commands should be logged
 
--------------------------------------------------------------------------------------------------
 
 
#>


# The following command will run the init script when the module is imported

#Initialize-Script
        # Display some troubleshoot info
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Write-Verbose "MyInvocation info:
         
        MyCommand: $($MyInvocation.MyCommand)
        ScriptName: $($MyInvocation.ScriptName)
        PSScriptRoot: $($MyInvocation.PSScriptRoot)
        PSCommandPath: $($MyInvocation.PSCommandPath)
        InvocationName: $($MyInvocation.InvocationName)
        ScriptLineNumber: $($MyInvocation.ScriptLineNumber)
        "

<#
        $P_MyInvocation = $PSCmdlet.SessionState.PSVariable.Get('MyInvocation').Value
 
        Write-Verbose "Parent MyInvocation info:
         
        MyCommand: $($P_MyInvocation.MyCommand)
        ScriptName: $($P_MyInvocation.ScriptName)
        PSScriptRoot: $($P_MyInvocation.PSScriptRoot)
        PSCommandPath: $($P_MyInvocation.PSCommandPath)
        InvocationName: $($P_MyInvocation.InvocationName)
        ScriptLineNumber: $($P_MyInvocation.ScriptLineNumber)
        "
 #>

        $global:ErrorActionPreference = "Stop"

        if ($MyInvocation.ScriptName.Length -gt 4) {
            try {
                $Global:Script_Name = ($MyInvocation.ScriptName).Replace("$($MyInvocation.PSScriptRoot)\","")
                $Global:Script_Name = ($Global:Script_Name).Substring(0,($Global:Script_Name).Length-4)
            } catch {
                $Global:Script_Name = "Terminal"
            }
        } else {
            $Global:Script_Name = "Terminal"
        }

        $global:CT_DEST="C:\CT"  # Where the files are downloaded to
        $global:DateStamp = get-date -Format yyyyMMddTHHmmss # A formatted date strong

        $global:Output_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_output.log"  # The output
        $global:Transcript_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_transcript.log"  # The powershell transcript file
        $global:API_log = "$($CT_DEST)\logs\$($Script_Name)\$($DateStamp)_API.log"
        $global:Install_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_install.log"  # The powershell installation file

<# This next block is better placed in the script itself, not in this module
 
        write-output "********************** Script $($Script_Name) starting. **********************"
        Write-OutputLog "Output log : $($Output_log)" -verbose:$VerbosePreference
        Write-OutputLog "Transcript log : $($Transcript_log)" -verbose:$VerbosePreference
        Write-OutputLog "API log : $($API_log)" -verbose:$VerbosePreference
        Write-OutputLog "Install log (if used) : $($Install_log)" -verbose:$VerbosePreference
        Write-Output "Log files can be found at $($CT_DEST)\logs\$($Script_Name)\"
 
 #>

        try{

            # Check for a CT folder on the C: and if not, create it, however that location should already exist as part of the Start-Transcript command.
            if(-not( Test-Path -Path $CT_DEST )) {
                try{
                    mkdir $CT_DEST > $null
                    #Transcript-Log "New folder created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create folder $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -verbose:$VerbosePreference -ErrorAction Continue
                    #Stop-Transcript
                    exit 1
                }
            }

            if(-not( Test-Path -Path "$($CT_DEST)\logs" )) {
                try{
                    mkdir "$($CT_DEST)\logs" > $null
                    #Transcript-Log "New logs folder created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create logs folder in $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -verbose:$VerbosePreference -ErrorAction Continue
                    #Stop-Transcript
                    exit 3
                }
            }

            if(-not( Test-Path -Path "$($CT_DEST)\logs\$($Script_Name)" )) {
                try{
                    mkdir "$($CT_DEST)\logs\$($Script_Name)" > $null
                    #Transcript-Log "New logs folder for $($Script_Name) created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create logs folder for $($Script_Name) in $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -ErrorAction Continue
                    #Stop-Transcript
                    exit 4
                }
            }

            $global:CT_Reg_Path = "HKLM:\Software\CT\Monitoring"
            $global:CT_Reg_Key = "$($CT_Reg_Path)\$($Script_Name)"
            if(-not( Test-Path -Path $CT_Reg_Key )) {
                try{
                    $CTMonitoringReg = New-Item -Path $CT_Reg_Path -Name $Script_Name -Force
                    Set-ItemProperty -Path "HKLM:\Software\CT" -Name "CustomerNo" -Value $customer
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot create registry key at $($CT_Reg_Key). $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error "$($CTMonitoringReg)" -ErrorAction Continue
                    Write-Error $_
                    #Stop-Transcript
                    exit 5
                }
            }


            #Setup TLS 1.1 and 1.2
            $Name         = 'DisabledByDefault'
            $Value        = '0'

            if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" )) {
                try{
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.1" -Force | Out-Null
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" -Name "Client" -Force | Out-Null
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot set TLS 1.1." -ErrorAction Continue
                    Write-Error $_
                }
            } else {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null
            }

            if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" )) {
                try{
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.2" -Force | Out-Null
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" -Name "Client" -Force | Out-Null
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot set TLS 1.2." -ErrorAction Continue
                    Write-Error $_
                }
            } else {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null
            }


            # ComputerType will report if the machine is a workstation, DC, or non-DC server
            # 1 for workstations, 2 for DCs, and 3 for non-DC servers
            try {
                $ProductType = (Get-CimInstance -ClassName Win32_OperatingSystem -Debug:$DebugPreference).ProductType
                if($ProductType -eq 3) {
                    $global:ComputerType = "Server"
                } elseif ($ProductType -eq 2) {
                    $global:ComputerType = "DC"
                } else {
                    $global:ComputerType = "Workstation"
                }
            } catch {
                Write-Error "There is a problem with this computer and updates are required for this script to continue." -ErrorAction Continue
                $PScmdlet.ThrowTerminatingError($_)
            }
            
            

            # Create Transcript header
            Write-Verbose "**********************"
            Write-Verbose "Script: $($Script_Name)."
            Write-Verbose "Start time: $($DateStamp)"
            Write-Verbose "Username: $($env:USERDOMAIN)\$($env:USERNAME)"
            Write-Verbose "Execution Policy Preference: $($env:PSExecutionPolicyPreference)"
            Write-Verbose "Machine: $($env:COMPUTERNAME) ($($env:OS))"
            Write-Verbose "Process ID: $($PID)"
            Write-Verbose "PSVersion: $($PSVersionTable.PSVersion)"
            Write-Verbose "PSEdition: $($PSVersionTable.PSEdition)"
            Write-Verbose "Operating System: $($PSVersionTable.OS)"
            Write-Verbose "WSManStackVersion: $($PSVersionTable.WSManStackVersion)"
            Write-Verbose "PSRemotingProtocolVersion: $($PSVersionTable.PSRemotingProtocolVersion)"
            Write-Verbose "SerializationVersion: $($PSVersionTable.SerializationVersion)"
            Write-Verbose "**********************"


            <#
            ---- END STANDARD SCRIPT BLOCK----
            #>

        } catch {
            $PScmdlet.ThrowTerminatingError($_)
        }


function Initialize-Script {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    Begin{
        # Display some troubleshoot info
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Write-Verbose "MyInvocation info:
         
        MyCommand: $($MyInvocation.MyCommand)
        ScriptName: $($MyInvocation.ScriptName)
        PSScriptRoot: $($MyInvocation.PSScriptRoot)
        PSCommandPath: $($MyInvocation.PSCommandPath)
        InvocationName: $($MyInvocation.InvocationName)
        ScriptLineNumber: $($MyInvocation.ScriptLineNumber)
        "


        $P_MyInvocation = $PSCmdlet.SessionState.PSVariable.Get('MyInvocation').Value

        Write-Verbose "Parent MyInvocation info:
         
        MyCommand: $($P_MyInvocation.MyCommand)
        ScriptName: $($P_MyInvocation.ScriptName)
        PSScriptRoot: $($P_MyInvocation.PSScriptRoot)
        PSCommandPath: $($P_MyInvocation.PSCommandPath)
        InvocationName: $($P_MyInvocation.InvocationName)
        ScriptLineNumber: $($P_MyInvocation.ScriptLineNumber)
        "


        $global:ErrorActionPreference = "Stop"

        if ($MyInvocation.ScriptName.Length -gt 4) {
            try {
                $Global:Script_Name = ($MyInvocation.ScriptName).Replace("$($MyInvocation.PSScriptRoot)\","")
                $Global:Script_Name = ($Global:Script_Name).Substring(0,($Global:Script_Name).Length-4)
            } catch {
                $Global:Script_Name = "Terminal"
            }
        } else {
            $Global:Script_Name = "Terminal"
        }

        $global:CT_DEST="C:\CT"  # Where the files are downloaded to
        $global:DateStamp = get-date -Format yyyyMMddTHHmmss # A formatted date strong

        $global:Output_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_output.log"  # The output
        $global:Transcript_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_transcript.log"  # The powershell transcript file
        $global:API_log = "$($CT_DEST)\logs\$($Script_Name)\$($DateStamp)_API.log"
        $global:Install_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_install.log"  # The powershell installation file

    }

    Process {

<# This next block is better placed in the script itself, not in this module
 
        write-output "********************** Script $($Script_Name) starting. **********************"
        Write-OutputLog "Output log : $($Output_log)" -verbose:$VerbosePreference
        Write-OutputLog "Transcript log : $($Transcript_log)" -verbose:$VerbosePreference
        Write-OutputLog "API log : $($API_log)" -verbose:$VerbosePreference
        Write-OutputLog "Install log (if used) : $($Install_log)" -verbose:$VerbosePreference
        Write-Output "Log files can be found at $($CT_DEST)\logs\$($Script_Name)\"
 
 #>

        try{

            # Check for a CT folder on the C: and if not, create it, however that location should already exist as part of the Start-Transcript command.
            if(-not( Test-Path -Path $CT_DEST )) {
                try{
                    mkdir $CT_DEST > $null
                    #Transcript-Log "New folder created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create folder $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -verbose:$VerbosePreference -ErrorAction Continue
                    #Stop-Transcript
                    exit 1
                }
            }

            if(-not( Test-Path -Path "$($CT_DEST)\logs" )) {
                try{
                    mkdir "$($CT_DEST)\logs" > $null
                    #Transcript-Log "New logs folder created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create logs folder in $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -verbose:$VerbosePreference -ErrorAction Continue
                    #Stop-Transcript
                    exit 3
                }
            }

            if(-not( Test-Path -Path "$($CT_DEST)\logs\$($Script_Name)" )) {
                try{
                    mkdir "$($CT_DEST)\logs\$($Script_Name)" > $null
                    #Transcript-Log "New logs folder for $($Script_Name) created at $CT_DEST."
                }catch{
                    #Can't create the folder, therefore cannot continue
                    Write-Error "Cannot create logs folder for $($Script_Name) in $CT_DEST. $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error $_ -ErrorAction Continue
                    #Stop-Transcript
                    exit 4
                }
            }

            $global:CT_Reg_Path = "HKLM:\Software\CT\Monitoring"
            $global:CT_Reg_Key = "$($CT_Reg_Path)\$($Script_Name)"
            if(-not( Test-Path -Path $CT_Reg_Key )) {
                try{
                    $CTMonitoringReg = New-Item -Path $CT_Reg_Path -Name $Script_Name -Force
                    Set-ItemProperty -Path "HKLM:\Software\CT" -Name "CustomerNo" -Value $customer
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot create registry key at $($CT_Reg_Key). $($Error[0].Exception.Message)" -ErrorAction Continue
                    Write-Error "$($CTMonitoringReg)" -ErrorAction Continue
                    Write-Error $_
                    #Stop-Transcript
                    exit 5
                }
            }


            #Setup TLS 1.1 and 1.2
            $Name         = 'DisabledByDefault'
            $Value        = '0'

            if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" )) {
                try{
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.1" -Force | Out-Null
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" -Name "Client" -Force | Out-Null
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot set TLS 1.1." -ErrorAction Continue
                    Write-Error $_
                }
            } else {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null
            }

            if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" )) {
                try{
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.2" -Force | Out-Null
                    New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" -Name "Client" -Force | Out-Null
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
                }catch{
                    #Can't create the regkey, therefore cannot continue
                    Write-Error "Cannot set TLS 1.2." -ErrorAction Continue
                    Write-Error $_
                }
            } else {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null
            }


            # ComputerType will report if the machine is a workstation, DC, or non-DC server
            # 1 for workstations, 2 for DCs, and 3 for non-DC servers
            try {
                $ProductType = (Get-CimInstance -ClassName Win32_OperatingSystem -Debug:$DebugPreference).ProductType
                if($ProductType -eq 3) {
                    $global:ComputerType = "Server"
                } elseif ($ProductType -eq 2) {
                    $global:ComputerType = "DC"
                } else {
                    $global:ComputerType = "Workstation"
                }
            } catch {
                Write-Error "There is a problem with this computer and updates are required for this script to continue." -ErrorAction Continue
                $PScmdlet.ThrowTerminatingError($_)
            }
            
            

            # Create Transcript header
            Write-Verbose "**********************"
            Write-Verbose "Script: $($Script_Name)."
            Write-Verbose "Start time: $($DateStamp)"
            Write-Verbose "Username: $($env:USERDOMAIN)\$($env:USERNAME)"
            Write-Verbose "Execution Policy Preference: $($env:PSExecutionPolicyPreference)"
            Write-Verbose "Machine: $($env:COMPUTERNAME) ($($env:OS))"
            Write-Verbose "Process ID: $($PID)"
            Write-Verbose "PSVersion: $($PSVersionTable.PSVersion)"
            Write-Verbose "PSEdition: $($PSVersionTable.PSEdition)"
            Write-Verbose "Operating System: $($PSVersionTable.OS)"
            Write-Verbose "WSManStackVersion: $($PSVersionTable.WSManStackVersion)"
            Write-Verbose "PSRemotingProtocolVersion: $($PSVersionTable.PSRemotingProtocolVersion)"
            Write-Verbose "SerializationVersion: $($PSVersionTable.SerializationVersion)"
            Write-Verbose "**********************"


            <#
            ---- END STANDARD SCRIPT BLOCK----
            #>

        } catch {
            $PScmdlet.ThrowTerminatingError($_)
        }
    }

}


Function Input-Box {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
        $message,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
        $title,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        $defaultvalue
    )
    Process{
        [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
        Add-Type -AssemblyName Microsoft.VisualBasic

        return [Microsoft.VisualBasic.Interaction]::InputBox($message, $title, $defaultvalue)
    }
}



Function Write-OutputLog {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        $output
    )
    Process{
        if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true) {
            Write-Verbose "(Line $($MyInvocation.ScriptLineNumber)) $($output)"
        }
        $output | Out-File -FilePath "$($Output_log)" -Append
    }
}


# Writes to the API log and optionally console if -Verbose flag is set at script level
Function Write-APILog {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        $output
    )
    Process{
        if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true) {
            Write-Verbose "(Line $($MyInvocation.ScriptLineNumber)) $($output)"
        }
        $output | Out-File -FilePath "$($API_log)" -Append
    }
}


Function Request-Download {
    # Downloads a file using BITS if possible, and if BITS is not available, downloads directly from URL
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [string[]] $FILE_URL,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [string[]] $FILE_LOCAL,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        [switch] $NoBITS, # This is for when BITS should not be used
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        [string[]] $BasicUsername, # This is for auth for downloading
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        [String[]] $BasicPassword # This is for auth for downloading
    )

    Process{
        if ($BasicUsername -and $BasicPassword) {
            $Credentials = New-Object System.Management.Automation.PSCredential ($userName, (ConvertTo-SecureString $BasicPassword -AsPlainText -Force))
        }
        # Test for existing file and remove if it exists
        if(Test-Path -Path $FILE_LOCAL -PathType Leaf ) {
            try {
                Remove-Item $FILE_LOCAL -Force
            } catch {
                #Can't remove the MSI, therefore cannot continue
                write-host "Cannot remove $($FILE_LOCAL). Unable to continue."
                Write-host $_
                $PScmdlet.ThrowTerminatingError($_)
            }
        }
        
        try {
            $ComputerType = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType
        } catch {
            write-host "There is a problem with this computer and updates are required for this script to continue."
            $PScmdlet.ThrowTerminatingError($_)
        }

        try {
            if ($ComputerType -ne 1) {
                Install-WindowsFeature BranchCache | Out-Null
            }
        } catch {
            $NoBITS = $true
        }

        if (!(Get-Module -ListAvailable -Name "BitsTransfer") -and !($NoBITS)) {
            try{
                Import-Module BitsTransfer -Force
            } catch {
                $NoBITS = $true
            }
        }



        if (!($NoBITS)) {
            # Check if BranchCache Distributed Mode is enabled, and if not, enable it so BITS uses computers on the subnet to download where available
            $BCStatus = Get-BCStatus
            if ($BCStatus.ClientConfiguration.CurrentClientMode -ne "DistributedCache") {
                try {
                    Enable-BCDistributed -Verbose -Force
                    Write-OutputLog "BranchCache Distributed Mode is now enabled" -Verbose:$VerbosePreference
                } catch {
                    #BranchCache cannot be enabled to work with BITS. BITS will download over the internet connection instead of cached copies on the local subnet
                    Write-OutputLog "Cannot enable BranchCache Distributed Mode. $($Error[0].ErrorDetails). The installation files will download over the internet connection instead of cached copies on the local subnet" -Verbose:$VerbosePreference
                }
            } else {
                Write-OutputLog "BranchCache Distributed Mode is already enabled in distributed mode on this computer" -Verbose:$VerbosePreference
            }
            try {
                if ($Credentials) {
                    $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)" -Credential $Credentials
                } else {
                    $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)"
                }
                
                #Complete-BitsTransfer -BitsJob $DownloadJob
                Write-OutputLog "Downloaded $($FILE_URL) using BITS to $($FILE_LOCAL)" -Verbose:$VerbosePreference
            } catch {
                Write-OutputLog "Cannot download $($FILE_URL) using BITS. Now trying through standard HTTP request." -Verbose:$VerbosePreference
                Write-OutputLog "$($_ | Out-String)" -Verbose:$VerbosePreference
                try {
                    if ($Credentials) {
                        $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -Credential $Credentials
                    } else {
                        $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru
                    }
                } catch {
                    Write-OutputLog "Cannot download $($FILE_URL) using standard HTTP request." -Verbose:$VerbosePreference
                    Write-OutputLog "$($_ | Out-String)" -Verbose:$VerbosePreference
                    $PScmdlet.ThrowTerminatingError($_)
                }
            }
        } else {
            try {
                if ($Credentials) {
                    $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -Credential $Credentials
                } else {
                    $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru
                }
        } catch {
                Write-OutputLog "Cannot download $($FILE_URL) using standard HTTP request." -Verbose:$VerbosePreference
                Write-OutputLog "$($_ | Out-String)" -Verbose:$VerbosePreference
                $PScmdlet.ThrowTerminatingError($_)
            }
        }
        return $DownloadJob

    }
}


function Get-CurrentLineNumber {
    # Downloads a file using BITS if possible, and if BITS is not available, downloads directly from URL
    [CmdletBinding()]
    param()
    #$LineNumber =
    #$LineNo = Get-ChildItem $MyInvocation.ScriptLineNumber
    return $PSCmdlet.MyInvocation.ScriptLineNumber
}



function Update-WMF {
    [CmdletBinding()]
    param(
        [Parameter()] [switch] $ForceReboot # Forces a reboot of the machine after update has completed
    )


    $OSInfo = (Get-WMIObject win32_operatingsystem)
    $OSBuild = $OSInfo.buildnumber
    $OSArch = $OSInfo.OSArchitecture
    $PowerShellVersion = $PSVersionTable.PSVersion.Major + ($PSVersionTable.PSVersion.Minor/10)
    $dotnetversion = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" -Name Release).Release
    Write-Host "DotNet Framework version $($dotnetversion) found."
    Write-Host "Powershell version $($PSVersionTable.PSVersion.ToString()) found."
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    
    if ($dotnetversion -lt 379893) {
        Write-Host "Updating DotNet Framework to 4.5.2"
        $dotnet_URL = "https://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
        $dotnet_File = "C:\CT\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
        try {
            Invoke-WebRequest -Uri $dotnet_URL -OutFile $dotnet_File -Verbose:$VerbosePreference
        } catch {
            write-host "Cannot download DotNet Framework 4.5.2. $_"
            $PScmdlet.ThrowTerminatingError($_)
        }

        write-host "Installing DotNet Framework 4.5.2"
        $DotNetInstall = Start-Process -FilePath $dotnet_File -ArgumentList "/q /norestart" -Wait -NoNewWindow -PassThru -Verbose:$VerbosePreference
        if (@(0,3010) -contains $DotNetInstall.ExitCode) {
            write-host "DotNet Framework 4.5.2 installed successfully. A reboot of this computer is required to complete the installation."
        } else {
            write-host "Unable to install DotNet Framework 4.5.2. Error code $($DotNetInstall.ExitCode) - $_"
            #Stop-Transcript
            $PScmdlet.ThrowTerminatingError($_)
        }
    }



    if($OSBuild -eq "9600" -and $PowerShellVersion -lt 5.1) {
        # Windows 8.1 and Windows Server 2012r2
        $WMF_URL = "https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1AndW2K12R2-KB3191564-x64.msu"
    } elseif($OSBuild -eq "9200" -and $PowerShellVersion -lt 5.1) {
        # Windows 8.1 and Windows Server 2012r2
        if($OSArch -eq "64-bit"){
            $WMF_URL = "https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/W2K12-KB3191565-x64.msu"
        } else {
            $WMF_URL = "https://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1-KB3191564-x86.msu"
        }    
    }

    if ($WMF_URL) {
        
        $WMF_File = "C:\CT\WMF51.msu"

        # Test for existing WMF file and remove if it exists
        if(Test-Path -Path $WMF_File -PathType Leaf ) {
            try {
                Remove-Item $WMF_File -Force -Verbose:$VerbosePreference
                #write-host "Found old WMF update and removed."
            } catch {
                #Can't remove the WMF, therefore cannot continue
                write-host "Cannot remove $WMF_File. Unable to continue. $($Error[0].Exception.Message)"
                #Stop-Transcript
                $PScmdlet.ThrowTerminatingError($_)
            }
        }
        
        # Download WMF
        try {
            Invoke-WebRequest -Uri $WMF_URL -OutFile $WMF_File -Verbose:$VerbosePreference
        } catch {
            write-host "Cannot download WMF. $($WMFjob.ErrorContextDescription)"
            #Stop-Transcript
            $PScmdlet.ThrowTerminatingError($_)
        }
            

        write-host "Installing WMF"
        $WMFUpgrade = Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList "$($WMF_File) /quiet /norestart" -Wait -NoNewWindow -PassThru -Verbose:$VerbosePreference
        if (@(0,3010) -contains $WMFUpgrade.ExitCode) {
            write-host "WMF installed successfully. A reboot of this computer is required to complete the installation."
        } else {
            write-host "Unable to install WMF. Error code $($WMFUpgrade.ExitCode)"
            #Stop-Transcript
            $PScmdlet.ThrowTerminatingError($_)
        }
    }

    if ($ForceReboot) {
        Start-Sleep -Seconds 60
        Restart-Computer -Force -Verbose:$VerbosePreference
    }
    return $true

}



function Update-PowerShell {
    [CmdletBinding()]
    param(
        [Parameter()] [switch] $ForceReboot # Forces a reboot of the machine after update has completed
    )

    $WMFupgrade = $false
    $OSInfo = (Get-WMIObject win32_operatingsystem)
    $OSBuild = $OSInfo.buildnumber
    $PowerShellVersion = $PSVersionTable.PSVersion.Major + ($PSVersionTable.PSVersion.Minor/10)
    $dotnetversion = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" -Name Release).Release
    Write-Host "DotNet Framework version $($dotnetversion) found."
    Write-Host "Powershell version $($PSVersionTable.PSVersion.ToString()) found."
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    
    if ($dotnetversion -lt 379893) {
        $WMFupgrade = $true
    }



    if($OSBuild -eq "9600" -and $PowerShellVersion -lt 5.1) {
        # Windows 8.1 and Windows Server 2012r2
        $WMFupgrade = $true
    } elseif($OSBuild -eq "9200" -and $PowerShellVersion -lt 5.1) {
        # Windows 8.1 and Windows Server 2012r2
        $WMFupgrade = $true
    }

    if($WMFupgrade -eq $true) {
        try {
            Update-WMF 
        }
        catch {
            write-host "Failed to upgrade WMF to 5.1. Please install DotNet Framework 4.5.2 and WMF 5.1 before upgrading PowerShell"
            $PScmdlet.ThrowTerminatingError($_)
        }
    }

    Write-Host "Now will attempt to install latest PowerShell version alongside Windows PowerShell 5.1."
    try {
        Invoke-Expression -Command "& { $(Invoke-RestMethod -Uri 'https://aka.ms/install-powershell.ps1') } -UseMSI -Quiet" -Verbose:$VerbosePreference
        #iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI -Quiet"
    } catch {
        Write-Host "Unable to install latest PowerShell"
        Write-Host $_
        $PScmdlet.ThrowTerminatingError($_)
    }
    return $true

}


Function New-APIPost {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [string[]] $BASE_URL,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [string[]] $EndPoint_URL,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [Parameter()] $headers,
        [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Mandatory = $true)]
        [Parameter()] [hashtable] $PostData, # The hashtable that needs to be posted to the API
        [Parameter()] [int] $Retry = 2 # Attempts a retry of the post if it fails
    )

    # Post to API
    Write-OutputLog "Posting $($PostData.Count) items to API" -Verbose:$VerbosePreference
    Write-APILog "$($PostData | Out-String)" -Verbose:$VerbosePreference
    $nullfound = $false
    foreach ($APIData in $PostData) {
        #Write-OutputLog $member.name
        foreach ($APIentry in $APIData.GetEnumerator()) {
            Write-Host "$($APIentry.Name) : $($APIentry.Value)"
            if ($APIentry.Value -eq $null) {$nullfound = $true}
        }
        if($nullfound -ne $true) {
            $body = $APIData | ConvertTo-Json
        } else {
            $body = $null
        }
        Write-APILog $body -Verbose:$VerbosePreference
        if ($null -ne $body) {
            $retryCount = $Retry # performs a retry after 60 seconds if it fails
            do{
                try {
                    $SendToAPI = Invoke-WebRequest -URI "$($BASE_URL)$($EndPoint_URL)" -Method 'POST' -Headers $headers -Body $body -PassThru -Debug:$DebugPreference
                    $retryCount = 0
                    #$ReturnValue = $SendToAPI
                } catch {
                    Write-OutputLog $_.Exception.Message -Verbose:$VerbosePreference
                    Write-OutputLog $_.Exception -Verbose:$VerbosePreference
                    Write-OutputLog $SendToAPI -Verbose:$VerbosePreference
                    Write-OutputLog $body -Verbose:$VerbosePreference
                    
                    # Dig into the exception to get the Response details.
                    Write-OutputLog "API Error: $($SendToAPI.StatusCode) [$($_.Exception.Response.StatusCode.value__)] - $($_.Exception.Response.StatusDescription)" -Verbose:$VerbosePreference
                    $line = $_.InvocationInfo.ScriptLineNumber
                    #if ($DebugPreference -eq "Continue") { write-error "API error: $($_.Exception.Response.StatusCode.value__) - $($_.Exception.Response.StatusDescription)" -Category InvalidData -ErrorAction Continue }
                    #$exiterror = $_.Exception.Response.StatusCode.value__
                    Set-ItemProperty -Path "$($CT_Reg_Key)" -Name "API-Post-$($BASE_URL)$($EndPoint_URL)" -Value "$($_.Exception.Response.StatusCode.value__)"
                    start-sleep -Seconds 60
                    $retryCount = $retryCount - 1
                    if($retryCount -lt 1) {$PScmdlet.ThrowTerminatingError($_)}
                }
            } while ($retryCount -gt 0)
        }
    }
    
}




Function Get-AVStatus {

    <#
    .Synopsis
    Get anti-virus product information.
    .Description
    This command uses WMI via the Get-CimInstance command to query the state of installed anti-virus products. The default behavior is to only display enabled products, unless you use -All. You can query by computername or existing CIMSessions.
    .Example
    PS C:\> Get-AVStatus chi-win10
    Displayname : ESET NOD32 Antivirus 9.0.386.0
    ProductState : 266256
    Enabled : True
    UpToDate : True
    Path : C:\Program Files\ESET\ESET NOD32 Antivirus\ecmd.exe
    Timestamp : Thu, 21 Jul 2016 15:20:18 GMT
    Computername : CHI-WIN10
    .Example
    PS C:\> import-csv s:\computers.csv | Get-AVStatus -All | Group Displayname | Select Name,Count | Sort Count,Name
    Name Count
    ---- -----
    ESET NOD32 Antivirus 9.0.386.0 12
    ESET Endpoint Security 5.0 6
    Windows Defender 4
    360 Total Security 1
    Import a CSV file which includes a Computername heading. The imported objects are piped to this command. The results are sent to Group-Object.
    .Example
    PS C:\> $cs | Get-AVStatus | where {-Not $_.UptoDate}
    Displayname : ESET NOD32 Antivirus 9.0.386.0
    ProductState : 266256
    Enabled : True
    UpToDate : False
    Path : C:\Program Files\ESET\ESET NOD32 Antivirus\ecmd.exe
    Timestamp : Wed, 20 Jul 2016 11:10:13 GMT
    Computername : CHI-WIN11
    Displayname : ESET NOD32 Antivirus 9.0.386.0
    ProductState : 266256
    Enabled : True
    UpToDate : False
    Path : C:\Program Files\ESET\ESET NOD32 Antivirus\ecmd.exe
    Timestamp : Thu, 07 Jul 2016 15:15:26 GMT
    Computername : CHI-WIN81
    You can also pipe CIMSession objects. In this example, the output are enabled products that are not up to date.
    .Notes
    version: 1.1
    Learn more about PowerShell:
    http://jdhitsolutions.com/blog/essential-powershell-resources/
    .Inputs
    [string[]]
    [Microsoft.Management.Infrastructure.CimSession[]]
    .Outputs
    [pscustomboject]
    .Link
    Get-CimInstance
    #>

    
    [cmdletbinding(DefaultParameterSetName = "computer")]

    Param(
        #The name of a computer to query.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "computer"
            )]
        [ValidateNotNullorEmpty()]
        [string[]]$Computername = $env:COMPUTERNAME,

        #An existing CIMsession.
        [Parameter(ValueFromPipeline, ParameterSetName = "session")]
        [Microsoft.Management.Infrastructure.CimSession[]]$CimSession,

        #The default is enabled products only.
        [switch]$All
    )

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

        $CTPSModules = (Get-Module CT-PS-Standard -ListAvailable)

        $CTPSModPath = $CTPSModules[0].ModuleBase

        $AVSearchList = import-csv -Path "$($CTPSModPath)\antiviruslist.csv"
        Function ConvertTo-Hex {
            Param([int]$Number)
            '0x{0:x}' -f $Number
        }
        [system.Version]$OSVersion = (Get-WmiObject win32_operatingsystem -computername $Computername).version

        #initialize an hashtable of paramters to splat to Get-CimInstance
        IF ($OSVersion -ge [system.version]'6.0.0.0') 
        {
            Write-Verbose "OS Windows Vista/Server 2008 or newer detected."
            $cimParams = @{
                Namespace   = "root/SecurityCenter2"
                ClassName   = "AntiVirusProduct"
# ErrorAction = "Stop"
            }
        } 
        Else 
        {
            Write-Verbose "Windows 2000, 2003, XP detected" 
            $cimParams = @{
                Namespace   = "root/SecurityCenter"
                ClassName   = "AntiVirusProduct"
# ErrorAction = "Stop"
            }
        } # end IF ($OSVersion -ge 6.0)

        If ($All) {
            Write-Verbose "[BEGIN ] Getting all AV products"
        }

        $results = @()
    } #begin

    Process {

        #initialize an empty array to hold results
        $AV = @()

        Write-Verbose "[PROCESS] Using parameter set: $($pscmdlet.ParameterSetName)"
        Write-Verbose "[PROCESS] PSBoundparameters: "
        Write-Verbose ($PSBoundParameters | Out-String)

        if ($pscmdlet.ParameterSetName -eq 'computer') {
            foreach ($computer in $Computername) {

                Write-Verbose "[PROCESS] Querying $($computer.ToUpper())"
                #$cimParams.ComputerName = $computer
                Try {
                    $AV += Get-CimInstance @CimParams -Verbose
                }
                Catch {
                    Write-Warning "[$($computer.ToUpper())] $($_.Exception.Message)"
                    $cimParams.ComputerName = $null
                }

            } #foreach computer
        }
        else {
            foreach ($session in $CimSession) {

                Write-Verbose "[PROCESS] Using session $($session.computername.toUpper())"
                $cimParams.CimSession = $session
                Try {
                    $AV += Get-CimInstance @CimParams -Verbose
                }
                Catch {
                    Write-Warning "[$($session.computername.ToUpper())] $($_.Exception.Message)"
                    $cimParams.cimsession = $null
                }

            } #foreach computer
        }

        foreach ($item in $AV) {
            Write-Verbose "[PROCESS] Found $($item.Displayname)"
            $hx = ConvertTo-Hex $item.ProductState
            $mid = $hx.Substring(3, 2)
            if ($mid -match "00|01") {
                $Enabled = $False
            }
            else {
                $Enabled = $True
            }
            $end = $hx.Substring(5)
            if ($end -eq "00") {
                $UpToDate = $True
            }
            else {
                $UpToDate = $False
            }

            $results += $item | Select-Object Displayname, ProductState,
            @{Name = "Enabled"; Expression = { $Enabled } },
            @{Name = "UpToDate"; Expression = { $UptoDate } },
            @{Name = "Path"; Expression = { $_.pathToSignedProductExe } },
            Timestamp,
            @{Name = "Computername"; Expression = { $_.PSComputername.toUpper() } }

        } #foreach

        $hive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $computerName)
        $regPathList = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
                       "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    
        foreach($regPath in $regPathList) {
            if($key = $hive.OpenSubKey($regPath)) {
                if($subkeyNames = $key.GetSubKeyNames()) {
                    foreach($subkeyName in $subkeyNames) {
                        $productKey = $key.OpenSubKey($subkeyName)
                        $productName = $productKey.GetValue("DisplayName")
                        $productVersion = $productKey.GetValue("DisplayVersion")
                        $productComments = $productKey.GetValue("Comments")
                        if(($productName -match $filter) -or ($productComments -match $filter)) {
                            $resultObj = [PSCustomObject]@{
                                Host = $computerName
                                Product = $productName
                                Version = $productVersion
                                Comments = $productComments
                            }
                            $results += $resultObj
                        }
                    }
                }
            }
            $key.Close()
        }

    } #process

    End {
        If ($All) {
            $results
        }
        else {
            #filter for enabled only
            ($results).Where( { $_.enabled })
        }

        Write-Verbose "[END ] Ending: $($MyInvocation.Mycommand)"
    } #end

} #end function