CT-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 if the latest version isn't already on the device AND initialise the
script with all the standard features required by scripts, including all the log files for each transaction
Add any other modules that need to be imported to the array of ModuleNames AFTER this module using
the format @("CT-Standard","AzureAD","MSOnline","Other module names") etc.
Adding them to this array will mean the script will only install+import them if there are newer
versions of the module available in the PS repositories on the computer, which results in less
requirement for downloading modules.
*****************
 
$ModuleNames = @("CT-Standard")
foreach($ModuleName in $ModuleNames) {
    if((Get-InstalledModule -Name $ModuleName -ErrorAction SilentlyContinue) -eq $null) { Install-Module -Name $ModuleName -Force -AllowClobber -Verbose:$VerbosePreference -ErrorAction Stop } else { if((Get-InstalledModule -Name $ModuleName).version -ne (Find-Module -Name $ModuleName).version) { Update-Module -Name $ModuleName -Force -ErrorAction Stop }}
    Import-Module -Name $ModuleName -Force -Verbose:$VerbosePreference -ErrorAction Stop
}
 
*****************
If this does not work or fails (often because of issues with PowerShell default settings or PSGallery or NuGet are corrupted),
manually the code below on the impacted machine then re-run the script that uses this module.
*****************
 
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Register-PSRepository -Default -Verbose -ErrorAction Continue
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.208 -Force -ErrorAction Continue -Verbose
 
*****************
 
--------------------------------------------------------------------------------------------------
 
 
--------------------------------------------------------------------------------------------------
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 commands will run when the module is imported



# Log transcript to a standard file until a specific log file and folder is established

$global:ErrorActionPreference = "Stop"
$global:CT_DEST="C:\CT"  # Where the files are downloaded to
$global:DateStamp = get-date -Format yyyyMMddTHHmmss # A formatted date strong
$PSDefaultParameterValues['out-file:width'] = 2000 # Sets the output width of Out-File to 2000 characters

#write-host "PSCommandPath: $($global:PSCommandPath)"
#write-host "Env_CommandLine: $([environment]::CommandLine)"
#write-host "$($MyInvocation | select -Property * | Out-String)"
#write-host "$(Get-Host | select -Property * | Out-String)"





try {
    if($global:PSCommandPath) { $PSCmdPath = $global:PSCommandPath } else { $PSCmdPath = [environment]::CommandLine }
    #write-host "PSCmdPath: $($PSCmdPath)"
    #write-host "$($MyInvocation | select -Property * | Out-String)"
    #write-host "$(Get-Host | select -Property * | Out-String)"
    $global:Script_Path = (Split-Path $PSCmdPath -Parent).Replace("""","")
    #This next bit removes the file extension from the script name
    $tempScriptNameParts = ((((Split-Path $PSCmdPath -Leaf).Replace("""","")).Split(" "))[0]).Split(".") #Splits out any extra commands that follow the script name and split into array based on '.'
    $Global:Script_Name = ((((Split-Path $PSCmdPath -Leaf).Replace("""","")).Split(" "))[0]).Replace(".$($tempScriptNameParts[$tempScriptNameParts.Count-1])","")
    #$Global:Script_Name = ($Global:Script_Name).Substring(0,($Global:Script_Name).Length-4)
} catch {
    $global:Script_Path = "C:\CT"
    $Global:Script_Name = "Terminal_$(get-date -Format yyyyMMdd)"
    $line = $_.InvocationInfo.ScriptLineNumber
    write-Error "ERROR: [ML$($line)] There was a problem recognising the executing application name." -ErrorAction Continue
    write-Error -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo
}

Write-Verbose "Script_Name: $($Global:Script_Name)"

    
    


<#
try{
    if($VerbosePreference -ne "SilentlyContinue") {Start-Transcript "C:\CT\$($Global:Script_Name).log" -Force -Append | Out-Null}
} catch {
    $line = $_.InvocationInfo.ScriptLineNumber
    write-Error "ERROR: [ML$($line)] There was a problem starting transcription." -ErrorAction Continue
    write-Error -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo -ErrorRecord $_ -ErrorAction Continue
}
#>



try{
    Write-Verbose "CT Standard Module version: $((Get-InstalledModule -Name CT-Standard).version)"
} catch {
    $line = $_.InvocationInfo.ScriptLineNumber
    write-Error "ERROR: [ML$($line)] There was a problem retrieving the module version." -ErrorAction Continue
    write-Error -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo -ErrorRecord $_ -ErrorAction Continue
}


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

# 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 {
    $global:ComputerType = (Get-CimInstance -ClassName Win32_OperatingSystem -Debug:$DebugPreference).ProductType
} catch {
    Write-Output "There is a problem with this computer and updates are required for this script to continue."
    $line = $_.InvocationInfo.ScriptLineNumber
    write-Error "ERROR: [ML$($line)] There is a problem with this computer and updates are required for this script to continue.
    $($_)"
 -ErrorId "1001" -Category ObjectNotFound -CategoryReason "Cannot extract computer type from WMI Win32_OperatingSystem." -ErrorAction Continue
    #Stop-Transcript | Out-Null
    $exiterror = 1001
    throw "There is a problem with this computer and updates are required for this script to continue."
    throw $exiterror
}



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
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] Cannot create folder $CT_DEST. $($_)" -Category WriteError -CategoryReason "Cannot create folder $($CT_DEST)." -ErrorId "1002" -ErrorAction Continue
            Write-Error $_ -Verbose 4>&1 >> $global:Verbose_log -ErrorAction Continue
            #Stop-Transcript
            $exiterror = 1002
            throw $exiterror
            throw
        }
    }

    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
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] Cannot create logs folder in $CT_DEST. $($_)" -Category WriteError -CategoryReason "Cannot create logs folder in $($CT_DEST)." -ErrorId "1003" -ErrorAction Continue
            Write-Error $_ -Verbose 4>&1 >> $global:Verbose_log -ErrorAction Continue
            #Stop-Transcript
            $exiterror = 1003
            throw $exiterror
            throw
        }
    }

    if(-not( Test-Path -Path "$($CT_DEST)\logs\$($Global:Script_Name)" )) {
        try{
            mkdir "$($CT_DEST)\logs\$($Global:Script_Name)" > $null
            #Transcript-Log "New logs folder for $($Global:Script_Name) created at $CT_DEST."
        }catch{
            #Can't create the folder, therefore cannot continue
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] Cannot create logs folder for $($Global:Script_Name) in $($CT_DEST). $($_)" -Category WriteError -CategoryReason "Cannot create logs folder for $($Global:Script_Name) in $($CT_DEST)." -ErrorId "1004" -ErrorAction Continue
            Write-Error $_ -ErrorAction Continue
            #Stop-Transcript
            $exiterror = 1004
            throw $exiterror
            throw
        }
    }

    # Check if CT registry key exists
    $global:CT_Reg_Path = "HKLM:\Software\CT\Monitoring"
    $global:CT_Reg_Key = "$($CT_Reg_Path)\$($Global:Script_Name)"
    if(-not( Test-Path -Path $CT_Reg_Key )) {
        try{
            $CTMonitoringReg = New-Item -Path $CT_Reg_Path -Name $Global:Script_Name -Force
            Set-ItemProperty -Path "HKLM:\Software\CT" -Name "CustomerNo" -Value $customer
        }catch{
            #Can't create the regkey, therefore cannot continue
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] Cannot create registry key at $($CT_Reg_Key). $($_)" -ErrorId "1005" -Category WriteError -CategoryReason "Cannot create registry key $($CT_Reg_Path)." -ErrorAction Continue
            Write-Error "$($CTMonitoringReg)" -ErrorAction Continue
            Write-Error $_
            #Stop-Transcript
            $exiterror = 1005
            throw $exiterror
        }
    }


    # Set global CT WMI class
    $global:CT_WMI_Class = "CT_$($Global:Script_Name)"



    
    # --------------------------------------------------------------------------------------------------
    # Installs NuGet 2.8.5.201 if its not already on there
    #$line = Get-CurrentLineNumber
    if(-not( Test-Path -Path "$($CT_DEST)\nuget.lck" )) {
        "$($DateStamp) lock" > "$($CT_DEST)\nuget.lck"
        Write-Verbose "Checking NuGet version for minimum v2.8.5.201" 
        try{
            $NuGet = Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue
            if($null -eq $NuGet) {
                Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
            }else{
                if($NuGet.Version -lt [System.Version]"2.8.5.201") { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force } else { Write-Verbose "NuGet version $($NuGet.Version) found." }
            }
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Unable to install NuGet 2.8.5.201. Will try again later. Continuing" -Line $line -ShowAsWarning
            Write-Verbose $_ -Line $line -ShowAsWarning
        }
        try{
            Remove-Item -Path "$($CT_DEST)\nuget.lck"
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] There was a problem removing the NuGet update lock file." -ErrorAction Continue
            write-Error -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo -ErrorRecord $_ -ErrorAction Continue
        }
    }
    
    
    
    # --------------------------------------------------------------------------------------------------
    # Installs PowerShellGet v2.2.5 as a minimum and v3.0 side-by-side
    #$line = Get-CurrentLineNumber
    if(-not( Test-Path -Path "$($CT_DEST)\psget.lck" )) {
        "$($DateStamp) lock" > "$($CT_DEST)\psget.lck"
        Write-Verbose "Checking PowerShellGet versions for minimum v2.2.5"
        try{
            $PSGet225Exists = $false
            $PSGet3Version = $null
            $PSGetInstalled = Get-Module -Name PowerShellGet -ListAvailable -ErrorAction SilentlyContinue
            foreach($PSGet in $PSGetInstalled) {
                if($PSGet.Version -ge [System.Version]"2.2.5" -and $PSGet.Version -lt [System.Version]"3.0.0") { $PSGet225Exists = $true }
                if($PSGet.Version -gt $PSGet3Version) { $PSGet3Version = $PSGet.Version }
            }
            if($PSGet225Exists -eq $false) {
                Install-Module -Name PowerShellGet -MinimumVersion 2.2.5 -Force -AllowClobber -ErrorAction Stop -Verbose #3>&14>&1 >> $Verbose_log
            }
            if($null -eq $PSGet3Version) {
                Install-Module -Name PowerShellGet -MinimumVersion 3.0.0 -Force -AllowClobber -AllowPrerelease -ErrorAction Stop -Verbose #3>&14>&1 >> $Verbose_log
            } else {
                $PSGet3Repo = [System.Version]"$((((Find-Module -Name PowerShellGet -AllowPrerelease).version).split("-"))[0])"
                if($PSGet3Repo -gt $PSGet3Version) {Update-Module -Name PowerShellGet -AllowPrerelease -Force -ErrorAction Stop -Verbose #3>&14>&1 >> $Verbose_log
                }
            }
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Unable to update PowerShellGet. Will try again later. Continuing" -Line $line -ShowAsWarning
            Write-Verbose $_ -Line $line -ShowAsWarning
        }
        try{
            Remove-Item -Path "$($CT_DEST)\psget.lck"
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            write-Error "ERROR: [ML$($line)] There was a problem removing the PowerShellGet update lock file." -ErrorAction Continue
            write-Error -ErrorRecord $_ -ErrorAction Continue # -Category $_.CategoryInfo -ErrorRecord $_ -ErrorAction Continue
        }
    }

    #$InteractiveScript = [Environment]::UserInteractive

    try{
        #$line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Checking TLS1.2 is set correctly" #-Line $line
        if((Get-TLS12Status) -eq $false){
            Write-Verbose "TLS1.2 not set correctly. Script may experience errors."
        }
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "TLS1.2 not set correctly"
        Write-Verbose $_
    }






    # Create a Transcript header for the verbose log
    if ($VerbosePreference -ne "SilentlyContinue") {
        Write-Host "--------------------------------------------------------------------------------------------------" #-MinimalOutput
        Write-Host "Script Name: $($Global:Script_Name)" #-MinimalOutput
        Write-Host "Start time: $($Global:DateStamp)" #-MinimalOutput
        Write-Host "Username: $($env:USERDOMAIN)\$($env:USERNAME)" #-MinimalOutput
        Write-Host "Execution Policy Preference: $($env:PSExecutionPolicyPreference)" #-MinimalOutput
        Write-Host "Machine: $($env:COMPUTERNAME) ($($env:OS))" #-MinimalOutput
        Write-Host "Process ID: $($PID)" #-MinimalOutput
        Write-Host "PSVersion: $($PSVersionTable.PSVersion)" #-MinimalOutput
        Write-Host "PSEdition: $($PSVersionTable.PSEdition)" #-MinimalOutput
        Write-Host "Operating System: $($PSVersionTable.OS)" #-MinimalOutput
        Write-Host "WSManStackVersion: $($PSVersionTable.WSManStackVersion)" #-MinimalOutput
        Write-Host "PSRemotingProtocolVersion: $($PSVersionTable.PSRemotingProtocolVersion)" #-MinimalOutput
        Write-Host "SerializationVersion: $($PSVersionTable.SerializationVersion)" #-MinimalOutput
        #Write-Verbose "Interactive Mode: $(Test-InteractiveMode)"
        Write-Host "Log files can be found at : $($CT_DEST)\logs\$($Global:Script_Name)" #-MinimalOutput
        Write-Host "--------------------------------------------------------------------------------------------------" #-MinimalOutput
    }
    


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

} catch {
    $line = $_.InvocationInfo.ScriptLineNumber
    Write-Error "ERROR: [ML$($line)] $($_)" -ErrorAction Stop
    Throw
}

#} #End process block


# This ends the script block that run on module import
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# This is used to check for a script lock file and return whether the script lock file exists or not
# This function is used to determine if the same script is already running in another PS instance
Function Test-ScriptLock {
    [CmdletBinding()]
    Param(
        [switch]$SetLock, # Enable this switch to SET the script lock file if it doesn't exist. Function will still return False if the lock file didn't exist before it created it
        [string]$LockFile # Use this to specify a different lock filename than the filename "$script_name.lck"
    )
    Process{
        try {

            #Test for Lockfile variable first, and if not set, set it as the filename "$script_name.lck".
            if(!$LockFile) { $LockFile = "$($Global:Script_Name).lck" }
            $LockFile = "C:\CT\$($LockFile)"
            Write-Verbose "Checking for script lock file at '$($Global:Script_Name)'"

            if(Test-Path -Path $LockFile) {
                return $true
            } else {
                # No lock file found
                if($SetLock) { "$($global:DateStamp) lock" > $LockFile }
                return $false
            }
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Output "Cannot determine if a lock file exists at $($LockFile)." -ShowAsError -Line $line
            Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue
            $PScmdlet.ThrowTerminatingError($_)
        }
    }
}
# --------------------------------------------------------------------------------------------------
    





# --------------------------------------------------------------------------------------------------
# This is used to interactively request input from the CT staff member running the script using a visual input box
Function Get-UserInput {
[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)
}
}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# This is used to replace the inbuilt Write-Verbose with a function that also outputs to the $Verbose_Log file
Function Write-Verbose {
[CmdletBinding(SupportsShouldProcess=$true)]
[alias("Write-OutputLog","Write-Output","Write-APILog")]
Param(
    $Message = " ",
    $InputObject,
    $Line = "$($PSCmdlet.MyInvocation.ScriptLineNumber)",
    [switch]$NoEnumerate,
    [switch]$MinimalOutput,
    [switch]$ShowAsWarning,
    [switch]$ShowAsError
)
Begin {
    $VerboseOutput = $false
    if($PSCmdlet.MyInvocation.MyCommand -match "Write-Verbose") {
        $LogFile = $global:Verbose_log
        if($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true -or $global:VerbosePreference -ne "SilentlyContinue") {
            $VerboseOutput = $true
        }
    } elseif($PSCmdlet.MyInvocation.MyCommand -match "Write-APILog") {
        $LogFile = $global:API_log
        $APILog = $true
    } else {
        $LogFile = $global:Output_log
    }
}
Process{
    try {
        if($InputObject) {$Message = $InputObject}
        if ($ShowAsError) {
            $FColour = "Red"
        } elseif ($ShowAsWarning) {
            $FColour = "Yellow"
        } else {
            if($LogFile -eq $global:Verbose_log) {
                $FColour = "Cyan"
            } elseif($LogFile -eq $global:API_log) {
                $FColour = "Gray"
            } else {
                $FColour = "White"
            }
        }
        if ($null -eq $Message) {
            $Message = " "
        }
        <#
        if($MinimalOutput) {
            $MessageString = Format-Output $Message -Line $line -Invocation $PSCmdlet.MyInvocation -MinimalOutput
        } else {
            $MessageString = Format-Output $Message -Line $line -Invocation $PSCmdlet.MyInvocation
        }
        #>

        try {
            $filename = $Global:Script_Name
        } catch {
            $filename = "Terminal"
            Write-Verbose $_
        }
        if (!$MinimalOutput) {
            if ($ShowAsError) {
                $MessageString = "$(Get-Date -Format "yyyyMMddTHHmmss") ERROR in $($filename) on line [$($line)]`n"
            } elseif ($ShowAsWarning) {
                $MessageString = "$(Get-Date -Format "yyyyMMddTHHmmss") WARNING from $($filename) (line [$($line)])`n"
            } else {
                $MessageString = "$(Get-Date -Format "yyyyMMddTHHmmss") " # OUTPUT from $($filename)`n"
            }
        } else {
            $MessageString = ""
        }
        #write-host "$($Message.GetType())"
        Switch ($Message.GetType().Name)
        {
            "datetime" {$MessageString += "$(get-date -Date $Message -format FileDateTimeUniversal)`n"}
            "string" {$MessageString += "$($Message)`n"}
            "int" {$MessageString += "$($Message.ToString())`n"}
            "float" {$MessageString += "$($Message.ToString())`n"}
            "single" {$MessageString += "$($Message.ToString())`n"}
            "double" {$MessageString += "$($Message.ToString())`n"}
            "char" {$MessageString += "$($Message)`n"}
            "single" {$MessageString += "$($Message.ToString())`n"}
            "byte" {$MessageString += "$($Message.ToString())`n"}
            "long" {$MessageString += "$($Message.ToString())`n"}
            "decimal" {$MessageString += "$($Message.ToString())`n"}
            "bool" {$MessageString += "$($Message.ToString())`n"}
            "array" {
                if($NoEnumerate) {
                    $MessageString += $Message
                } else {
                    foreach($arrayitem in $Message){
                        $MessageString += "$($arrayitem | Out-String)`n"
                    }
                }
            }
            "hashtable" {
                #$MessageString += "`n"
                if($NoEnumerate) {
                    $MessageString += $Message
                } else {
                    foreach ($hash in $Message.GetEnumerator()) {
                    $MessageString += "$($hash.name) : $($hash.value)`n"
                    }
                }
            }
            "errorrecord" {
                if (!$MinimalOutput) {$MessageString = "$(Get-Date -Format "yyyyMMddTHHmmss") ERROR in $($Invocation.PSCommandPath) on line [$($line)]`n $($Message.CategoryInfo.Activity) : $($Message.ToString())`n"}
                $MessageString += "$($Message.InvocationInfo | out-string)`n"
                #$FColour = "Red"
            }
            default {
                #$MessageString += ($Message | ForEach-Object { "$($_)`n" })
                if($NoEnumerate) {
                    $MessageString += $Message
                } else {
                    $MessageString += ($Message | Out-String)
                }
            }
        }
        # Write to console if its Write-Output, Write-Verbose with -Verbose switch globally enabled, or if LogFile variable is null/doesn't exist
        try {
            if($LogFile -eq $global:Output_log -or $VerboseOutput -eq $true -or (!$LogFile)) { Write-Host -ForegroundColor $FColour "$($MessageString)" }
        } catch {
            # No console output possible
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Host -ForegroundColor Red "$(Get-Date -Format "yyyyMMddTHHmmss") ERROR in $($Invocation.PSCommandPath) on line [$($line)]`n Unable to display $($PSCmdlet.MyInvocation.MyCommand) output"
            Write-Error $_ -ErrorAction Continue
        }

    
        # Write to relevant log file
        try {
            if ($LogFile) { $MessageString | Out-File $LogFile -Append }
        } catch {
            # No log file available
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Host -ForegroundColor Red "$(Get-Date -Format "yyyyMMddTHHmmss") ERROR in $($Invocation.PSCommandPath) on line [$($line)]`n Unable to write to log file '$($LogFile)'"
            Write-Error $_ -ErrorAction Continue
        }


    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Host -ForegroundColor Red "$(Get-Date -Format "yyyyMMddTHHmmss") ERROR in $($Invocation.PSCommandPath) on line [$($line)]`n Unable to display $($PSCmdlet.MyInvocation.MyCommand) output"
        Write-Error $_ -ErrorAction Continue
    }
}
}
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
# This is used to download files using BITS, and if BITS is not available, directly using web calls.
Function Request-Download {
[CmdletBinding(SupportsShouldProcess=$true)]
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[]] $BasicPasswd, # This is for auth for downloading
    [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
    [Int32] $Retry = 2 # This is for auth for downloading

)

Process{
    Write-Verbose "Attempting to download $($FILE_URL) to $($FILE_LOCAL)." #-Verbose 4>&1 >> $global:Verbose_log

    if($Retry -lt 1) {$Retry = 2}
    if ($BasicUsername -and $BasicPasswd) {
        $Credentials = New-Object System.Management.Automation.PSCredential ($userName, (ConvertTo-SecureString $BasicPasswd -AsPlainText -Force))
    }
    # Test for existing file and remove if it exists
    if(Test-Path -Path $FILE_LOCAL) {
        try {
            Write-Verbose "Existing file found - renaming existing file." #-Verbose 4>&1 >> $global:Verbose_log
            $FILE_NAME = Split-Path $FILE_LOCAL -Leaf
            $FILE_EXT = ($FILE_NAME).Substring(($FILE_NAME).Length-4,($FILE_NAME).Length)   
            $FILE_NAME = ($FILE_NAME).Substring(0,($FILE_NAME).Length-4)
            $FILE_NAME = "$($FILE_NAME)_old$($global:DateStamp)$($FILE_EXT)"
            Rename-Item -Path $FILE_LOCAL -NewName $FILE_NAME -Verbose 4>&1 >> $global:Verbose_log
        } catch {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Output "Cannot remove $($FILE_LOCAL)." -ShowAsError -Line $line
            Write-Verbose $_
            $PScmdlet.ThrowTerminatingError($_)
        }
    }
    

    if (!($NoBITS)) {
        try {
            if ($ComputerType -ne 1) {
                Write-Verbose "Installing BranchCache." #-Verbose 4>&1 >> $global:Verbose_log
                Install-WindowsFeature BranchCache
            }
        } catch {
            $NoBITS = $true
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Cannot install BranchCache." -ShowAsWarning -Line $line
            Write-Verbose $_
        }
    }

    if (!(Get-Module -ListAvailable -Name "BitsTransfer") -and !($NoBITS)) {
        try{
            Write-Verbose "Importing BitsTransfer Module." #-Verbose 4>&1 >> $global:Verbose_log
            Import-Module BitsTransfer -Force -Verbose 4>&1 >> $global:Verbose_log
        } catch {
            $NoBITS = $true
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Cannot install BitsTranfer." -ShowAsWarning -Line $line
            Write-Verbose $_
        }
    }

    if (!($NoBITS)) {
        # Check if BranchCache and BITS is functional, and if not, switch to NoBITS mode
        try{
            Write-Verbose "Getting BranchCache status." #-Verbose 4>&1 >> $global:Verbose_log
            $BCStatus = Get-BCStatus #-Verbose 4>&1 >> $global:Verbose_log
        } catch {
            $NoBITS = $true
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "BranchCache and/or BITS is corrupt and cannot be used. Regular web transfer will be attempted instead." -ShowAsWarning -Line $line
            Write-Verbose $_

        }
    }



    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
        $BitsRetry = $Retry
        if ($BCStatus.ClientConfiguration.CurrentClientMode -ne "DistributedCache") {
            try {
                $BCEnable = Enable-BCDistributed -Force  -Verbose 4>&1
                Write-Verbose $BCEnable
                Write-Verbose "BranchCache Distributed Mode is now enabled" #-Verbose 4>&1 >> $global:Verbose_log
            } 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-output "Cannot enable BranchCache Distributed Mode. $($_). The installation files will download over the internet connection instead of cached copies on the local subnet" -Verbose 4>&1 >> $global:Verbose_log
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "Cannot enable BranchCache Distributed Mode." -ShowAsWarning -Line $line
                Write-Verbose $_
            }
        } else {
            Write-Verbose "BranchCache Distributed Mode is already enabled in distributed mode on this computer" #-Verbose 4>&1 >> $global:Verbose_log
        }
        while ($BitsRetry -gt 0) {
            
            Write-Verbose "Downloading $($FILE_URL) using BITS to $($FILE_LOCAL) - Attempt $(($Retry - $BitsRetry)+1) of $($Retry)" # -Verbose 4>&1 >> $global:Verbose_log
            try {
                if ($Credentials) {
                    $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)" -Credential $Credentials -Verbose 4>&1
                } else {
                    $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)" -Verbose 4>&1
                }
                Write-Verbose $DownloadJob
                
                #Complete-BitsTransfer -BitsJob $DownloadJob
                Write-Verbose "Downloaded $($FILE_URL) using BITS to $($FILE_LOCAL)" #-Verbose 4>&1 >> $global:Verbose_log
                $BitsRetry = 0
            } catch {
                $line = $_.InvocationInfo.ScriptLineNumber
                #write-output "Cannot download $($FILE_URL) using BITS. Now trying through standard HTTP request." -Verbose 4>&1 >> $global:Verbose_log
                #write-output "$($_ | Out-String)" -Verbose 4>&1 >> $global:Verbose_log
                if ($BitsRetry -le 1) {
                    Write-Output "Failed to download $($FILE_URL) using BITS. Switching to standard web request mode." -ShowAsError -Line $line
                    Write-Verbose $_ -ShowAsError
                    $NoBITS = $true
                    $BitsRetry = $BitsRetry -1
                } else {
                    Write-Output "Unable to download $($FILE_URL) using BITS. Retrying." -ShowAsWarning -Line $line
                    Write-Verbose $_ -ShowAsWarning
                    $BitsRetry = $BitsRetry -1
                }
            }
        }
    } 
    if ($NoBITS) {
        $WebRetry = $Retry
        while ($WebRetry -gt 0) {
            
            Write-Verbose "Downloading $($FILE_URL) using standard web request to $($FILE_LOCAL) - Attempt $(($Retry - $WebRetry)+1) of $($Retry)" # -Verbose 4>&1 >> $global:Verbose_log
            try {
                if ($Credentials) {
                    $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -Credential $Credentials -UseBasicParsing -Verbose 4>&1
                } else {
                    $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -UseBasicParsing -Verbose 4>&1
                }
                Write-Verbose $DownloadJob
                Write-Verbose "Downloaded $($FILE_URL) using standard web request to $($FILE_LOCAL)" # -Verbose 4>&1 >> $global:Verbose_log
                $WebRetry = 0
            } catch {
                $line = $_.InvocationInfo.ScriptLineNumber
                if ($WebRetry -le 1) {
                    Write-Output "Failed to download $($FILE_URL) using standard web request." -ShowAsError -Line $line
                    Write-Verbose $_ -ShowAsError
                    $WebRetry = $WebRetry -1
                    $PScmdlet.ThrowTerminatingError($_)
                } else {
                    Write-Output "Unable to download $($FILE_URL) using standard web request. Retrying." -ShowAsWarning -Line $line
                    Write-Verbose $_ -ShowAsWarning
                    $WebRetry = $WebRetry -1
                }
            }
        }
    }
    return $DownloadJob

}
}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# Returns current script line number
function Get-CurrentLineNumber {
[CmdletBinding()]
[alias("Get-CurrentLine")]
param()
return $PSCmdlet.MyInvocation.ScriptLineNumber
}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# Updates WMF on the machine to minimum 5.1
function Update-WMF {
[CmdletBinding(SupportsShouldProcess=$true)]
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-Verbose "DotNet Framework version $($dotnetversion) found." #-Verbose 4>&1 >> $global:Verbose_log
Write-Verbose "Powershell version $($PSVersionTable.PSVersion.ToString()) found." #-Verbose 4>&1 >> $global:Verbose_log
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

if ($dotnetversion -lt 379893) {
    Write-Verbose "Updating DotNet Framework to 4.5.2" #-Verbose 4>&1 >> $global:Verbose_log
    $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 4>&1 >> $global:Verbose_log -UseBasicParsing
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Output "Cannot download DotNet Framework 4.5.2." -ShowAsError -Line $line # -ErrorId $_.Exception.HResult -Category ConnectionError -ErrorAction Continue
        Write-Output $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)
    }

    Write-Verbose "Installing DotNet Framework 4.5.2" #-Verbose 4>&1 >> $global:Verbose_log
    try {
        $DotNetInstall = Start-Process -FilePath $dotnet_File -ArgumentList "/q /norestart" -Wait -NoNewWindow -PassThru -Verbose 4>&1 >> $global:Verbose_log
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
    }
    if (@(0,3010) -contains $DotNetInstall.ExitCode) {
        Write-Verbose "DotNet Framework 4.5.2 installed successfully. A reboot of this computer is required to complete the installation."
    } else {
        #Write-Output "Unable to install DotNet Framework 4.5.2. Error code $($DotNetInstall.ExitCode) - $_"
        Write-Output "Unable to install DotNet Framework 4.5.2."  -ShowAsError -Line $line
        Write-Output $_ -ShowAsError -Line $line  # -ErrorId $DotNetInstall.ExitCode -ErrorAction Continue
        #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 4>&1 >> $global:Verbose_log
            #Write-Output "Found old WMF update and removed."
        } catch {
            #Can't remove the WMF, therefore cannot continue
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Output "Cannot remove $($WMF_File)." -ShowAsError -Line $line
            Write-Output $_ -ShowAsError -Line $line
            #Write-Output "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 -UseBasicParsing -Verbose 4>&1 >> $global:Verbose_log 
    } catch {
        #Write-Verbose "Cannot download WMF. $($WMFjob.ErrorContextDescription)"
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Output "Cannot download WMF." -ShowAsError -Line $line
        Write-Verbose $_ -ShowAsError -Line $line # -ErrorId $_.Exception.HResult -ErrorAction Continue
        #Stop-Transcript
        $PScmdlet.ThrowTerminatingError($_)
    }
        

    Write-Output "Installing WMF"
    $WMFUpgrade = Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList "$($WMF_File) /quiet /norestart" -Wait -NoNewWindow -PassThru -Verbose 4>&1 >> $global:Verbose_log
    if (@(0,3010) -contains $WMFUpgrade.ExitCode) {
        Write-Verbose "WMF installed successfully. A reboot of this computer is required to complete the installation."
    } else {
        $line = Get-CurrentLineNumber
        Write-Output "Cannot download WMF." -ShowAsError -Line $line
        Write-Verbose $_  -ShowAsError -Line $line # -ErrorId $WMFUpgrade.ExitCode -ErrorAction Continue
        #Write-Output "Unable to install WMF. Error code $($WMFUpgrade.ExitCode)"
        #Stop-Transcript
        $PScmdlet.ThrowTerminatingError($_)
    }
}

if ($ForceReboot) {
    Start-Sleep -Seconds 60
    Restart-Computer -Force -Verbose 4>&1 >> $global:Verbose_log
}
return $true

}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# Updates PowerShell on the machine to minimum 5.1 and then downloads newer version if available
function Update-PowerShell {
[CmdletBinding(SupportsShouldProcess=$true)]
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-Output "DotNet Framework version $($dotnetversion) found."
Write-Output "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 {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Output "Failed to upgrade WMF to 5.1. Please install DotNet Framework 4.5.2 and WMF 5.1 before upgrading PowerShell"
        $PScmdlet.ThrowTerminatingError($_)
    }
}

Write-Output "Now will attempt to install latest PowerShell version alongside Windows PowerShell 5.1."
try {
    $PSInstall = Invoke-Expression -Command "& { $(Invoke-RestMethod -Uri 'https://aka.ms/install-powershell.ps1') } -UseMSI -Quiet" -Verbose 4>&1
    Write-Verbose $PSInstall
    #iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI -Quiet"
} catch {
    $line = $_.InvocationInfo.ScriptLineNumber
    Write-Output "Unable to install latest PowerShell." -ShowAsError -Line $line # -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue
    Write-Output $_
    $PScmdlet.ThrowTerminatingError($_)
}
return $true

}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# Posts to an API and logs the result
Function New-APIPost {
[CmdletBinding(SupportsShouldProcess=$true)]
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-output "Posting $($PostData.Count) items to API" -Verbose 4>&1 >> $global:Verbose_log
Write-APILog "$($PostData | Out-String)" -Verbose 4>&1 >> $global:Verbose_log
$nullfound = $false
foreach ($APIData in $PostData) {
    #write-output $member.name
    foreach ($APIentry in $APIData.GetEnumerator()) {
        Write-Output "$($APIentry.Name) : $($APIentry.Value)"
        if ($null -eq $APIentry.Value) {$nullfound = $true}
    }
    if($nullfound -ne $true) {
        $body = $APIData | ConvertTo-Json
    } else {
        $body = $null
    }
    Write-APILog $body -Verbose 4>&1 >> $global:Verbose_log
    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 -Verbose 4>&1 >> $global:Verbose_log 
                $retryCount = 0
                #$ReturnValue = $SendToAPI
            } catch {
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "API Error $($SendToAPI.StatusCode): [$($_.Exception.Response.StatusCode.value__)] - $($_.Exception.Response.StatusDescription)." -ShowAsError -Line $line
                Write-Verbose $_  -ShowAsError -Line $line
                Write-Verbose $_.Exception.Message -ShowAsError -Line $line
                Write-Verbose $_.Exception -ShowAsError -Line $line
                Write-Verbose $SendToAPI -ShowAsError -Line $line
                Write-Verbose $body -ShowAsError -Line $line
                Write-Output $_ -ShowAsError -Line $line
                
                # Dig into the exception to get the Response details.
                #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)
    }
}

}
# --------------------------------------------------------------------------------------------------






# --------------------------------------------------------------------------------------------------
# Gets the current installed AV and its status
Function Get-AVStatus {

[cmdletbinding(SupportsShouldProcess=$true,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"

    #$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" #-Verbose 4>&1 >> $global:Verbose_log
        $cimParams = @{
            Namespace   = "root/SecurityCenter2"
            ClassName   = "AntiVirusProduct"
# ErrorAction = "Stop"
        }
    } 
    Else 
    {
        Write-Verbose "Windows 2000, 2003, XP detected" #-Verbose 4>&1 >> $global:Verbose_log
        $cimParams = @{
            Namespace   = "root/SecurityCenter"
            ClassName   = "AntiVirusProduct"
# ErrorAction = "Stop"
        }
    } # end IF ($OSVersion -ge 6.0)

    #Test for SecurityCenter(2) existance and if not, run as server
    try {
        $CIMTest = Get-CimInstance @CimParams -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
        if ($CIMTest) {
            $runAsServer = $False
            Write-Verbose "$($cimParams.Namespace) found in WMI" #-Verbose 4>&1 >> $global:Verbose_log
        } else {
            $runAsServer = $True
            Write-Verbose "$($cimParams.Namespace) not found in WMI" #-Verbose 4>&1 >> $global:Verbose_log
        }
    } catch {
        $runAsServer = $True
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "$($cimParams.Namespace) not found in WMI" -Line $line
    }

    If ($All) {
        Write-Verbose "Getting all AV products" #-Verbose 4>&1 >> $global:Verbose_log
    }


    $results = @()
} #begin

Process {

    try {

        #Check against WMI if workstation
        if($ComputerType -eq 1 -and $runAsServer -eq $False) {

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

            Write-Verbose "PROCESS" #-Verbose 4>&1 >> $global:Verbose_log
            Write-Verbose "Using parameter set: $($pscmdlet.ParameterSetName)" #-Verbose 4>&1 >> $global:Verbose_log
            Write-Verbose "PSBoundparameters: " #-Verbose 4>&1 >> $global:Verbose_log
            Write-Verbose ($PSBoundParameters | Out-String) #-Verbose 4>&1 >> $global:Verbose_log

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

                    Write-Verbose "Querying $($computer.ToUpper())" # -Verbose 4>&1 >> $global:Verbose_log
                    #$cimParams.ComputerName = $computer
                    Try {
                        $AV += Get-CimInstance @CimParams -Verbose | Where-Object {$null -ne $_.displayName} 4>&1 >> $global:Verbose_log 
                    }
                    Catch {
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose $_ -Line $line
                        $cimParams.ComputerName = $null
                    }

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

                    Write-Verbose "Using session $($session.computername.toUpper())" #-Verbose 4>&1 >> $global:Verbose_log
                    $cimParams.CimSession = $session
                    Try {
                        $AV += Get-CimInstance @CimParams -Verbose 4>&1 >> $global:Verbose_log | Where-Object {$null -ne $_.displayName}
                    }
                    Catch {
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose $_ -Line $line
                        $cimParams.cimsession = $null
                    }

                } #foreach computer
            }

            foreach ($item in $AV) {
                Write-Verbose "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
                }

                if(!($item.pathToSignedProductExe)) {
                    $results += $item | Select-Object @{Name = "DisplayName"; Expression = { ($_.Displayname).trim() } }, ProductState,
                    @{Name = "Enabled"; Expression = { $Enabled } },
                    @{Name = "UpToDate"; Expression = { $UptoDate } },
                    @{Name = "Path"; Expression = { $_.pathToSignedProductExe } },
                    @{Name = "Version"; Expression = { $_.VersionNumber } },
                    Timestamp,
                    @{Name = "Computername"; Expression = { $_.PSComputername.toUpper() } }
                } else {
                    if($AVproduct.displayName -match "Defender"){
                        $DefenderInfo = Get-MpComputerStatus -Verbose 4>&1 >> $global:Verbose_log
                        $AVversion = $DefenderInfo.AMProductVersion
                    } else {
                        if(Test-Path -Path $item.pathToSignedProductExe) {
                            $AVversion = (Get-Item $item.pathToSignedProductExe -ErrorAction Stop).VersionInfo.fileversion
                            $results += $item | Select-Object @{Name = "DisplayName"; Expression = { ($_.Displayname).trim() } }, ProductState,
                            @{Name = "Enabled"; Expression = { $Enabled } },
                            @{Name = "UpToDate"; Expression = { $UptoDate } },
                            @{Name = "Path"; Expression = { $_.pathToSignedProductExe } },
                            @{Name = "Version"; Expression = { $AVversion } },
                            Timestamp,
                            @{Name = "Computername"; Expression = { $_.PSComputername.toUpper() } }
                        }
                    }
                }

            } #foreach
        
        } else {

            
            $ModulePath = (Get-Module -ListAvailable -Name CT-Standard)[0].ModuleBase
            Write-Verbose "ModulePath: $($ModulePath)"
            $vbsexe = Invoke-Expression -Command "CMD.exe /c CSCRIPT '$($ModulePath)\avstatus.vbs' WRITE" -Verbose 4>&1 >> $global:Verbose_log -ErrorAction Stop
            Write-Verbose "vbsexe: $($vbsexe)"

            $AV = @()
            $cimParams = @{
                Namespace   = "root/SecurityCenter"
                ClassName   = "AntiVirusProduct"
# ErrorAction = "Stop"
            }

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

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

                    Write-Verbose "Querying $($computer.ToUpper())"
                    #$cimParams.ComputerName = $computer
                    Try {
                        $AVtemp = Get-CimInstance @CimParams | Where-Object {$null -ne $_.displayName}
                        #Write-Verbose $AVtemp
                        $AV += $AVtemp
                    }
                    Catch {
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose $_ -ShowAsWarning -Line $line
                        $cimParams.ComputerName = $null
                    }

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

                    Write-Verbose "Using session $($session.computername.toUpper())"
                    $cimParams.CimSession = $session
                    Try {
                        $AVtemp = Get-CimInstance @CimParams #-Verbose 4>&1 >> $global:Verbose_log
                        #Write-Verbose $AVtemp
                        $AV += $AVtemp
                    }
                    Catch {
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose $_ -ShowAsWarning -Line $line
                        $cimParams.cimsession = $null
                    }

                } #foreach computer
            }

            foreach ($item in $AV) {
                Write-Verbose "Found $($item.Displayname)"
                Write-Verbose "$($item | Format-List -Property *)"
                $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
                }

                if(!($item.pathToSignedProductExe)) {
                    $results += $item | Select-Object @{Name = "DisplayName"; Expression = { ($_.Displayname).trim() } }, ProductState,
                    @{Name = "Enabled"; Expression = { $Enabled } },
                    @{Name = "UpToDate"; Expression = { $UptoDate } },
                    @{Name = "Path"; Expression = { $_.pathToSignedProductExe } },
                    @{Name = "Version"; Expression = { $_.VersionNumber } },
                    Timestamp,
                    @{Name = "Computername"; Expression = { $_.PSComputername.toUpper() } }
                } else {
                    if($AVproduct.displayName -match "Defender"){
                        $DefenderInfo = Get-MpComputerStatus -Verbose 4>&1 >> $global:Verbose_log
                        $AVversion = $DefenderInfo.AMProductVersion
                    } else {
                        if(Test-Path -Path $item.pathToSignedProductExe) {
                            $AVversion = (Get-Item $item.pathToSignedProductExe -ErrorAction Stop).VersionInfo.fileversion
                            $results += $item | Select-Object @{Name = "DisplayName"; Expression = { ($_.Displayname).trim() } }, ProductState,
                            @{Name = "Enabled"; Expression = { $Enabled } },
                            @{Name = "UpToDate"; Expression = { $UptoDate } },
                            @{Name = "Path"; Expression = { $_.pathToSignedProductExe } },
                            @{Name = "Version"; Expression = { $AVversion } },
                            Timestamp,
                            @{Name = "Computername"; Expression = { $_.PSComputername.toUpper() } }
                        }
                    }
                }

            } #foreach

        } #if/else
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Output "Unable to retrieve AV information" -ShowAsError -Line $line
        Write-Output $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)
    }

} #process

End {
    If ($All) {
        Write-Verbose "Returning:"
        Write-Verbose "$($results | Format-List -Property *)"
        return $results
    } else {
        #filter for enabled only
        Write-Verbose "Returning:"
        Write-Verbose "$(($results).Where( { $_.enabled }) | Format-List -Property *)"
        return ($results).Where( { $_.enabled })
    }

    Write-Verbose "END"
} #end

} #end function
# --------------------------------------------------------------------------------------------------





# --------------------------------------------------------------------------------------------------
# This will interactively prompt to select one or more items from a list

function Select-FromChoices {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Array of options")]
    [array] $CIMInput, # Array of options to present
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Title text")]
    [string] $TitleText, # Array of options to present
    [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Should the box be multi select or not?")]
    [bool] $Multiselect = $true # Multiselect?
)

Process{
    try{
        Add-Type -AssemblyName System.Windows.Forms
        Add-Type -AssemblyName System.Drawing
        
        $Screens = [System.Windows.Forms.Screen]::AllScreens
        $ScreenSize = $Screens[0].Bounds.Size
        $ScreenWidth = $ScreenSize.Width
        $ScreenHeight = $ScreenSize.Height
        $FormWidth = $ScreenWidth / 3
        if($FormWidth -lt 400) {$FormWidth = 400}
        $FormHeight = $ScreenHeight / 3
        if($FormHeight -lt 400) {$FormHeight = 400}
        $form = New-Object System.Windows.Forms.Form
        $form.Text = $TitleText
        $form.Size = New-Object System.Drawing.Size($FormWidth,$FormHeight)
        $form.StartPosition = 'CenterScreen'
        $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
        $form.MaximizeBox = $false
        $form.MinimizeBox = $false
        
        $OKButton = New-Object System.Windows.Forms.Button
        $OKVPos = $FormHeight-70
        $OKHPos = 20
        $OKButton.Location = New-Object System.Drawing.Point($OKHPos,$OKVPos)
        $OKButton.Size = New-Object System.Drawing.Size(75,23)
        $OKButton.Text = 'OK'
        $OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
        $form.AcceptButton = $OKButton
        $form.Controls.Add($OKButton)
        
        $CancelButton = New-Object System.Windows.Forms.Button
        $CancelVPos = $FormHeight-70
        $CancelHPos = $FormWidth-115
        $CancelButton.Location = New-Object System.Drawing.Point($CancelHPos,$CancelVPos)
        $CancelButton.Size = New-Object System.Drawing.Size(75,23)
        $CancelButton.Text = 'Cancel'
        $CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
        $form.CancelButton = $CancelButton
        $form.Controls.Add($CancelButton)
        
        $label = New-Object System.Windows.Forms.Label
        $label.Location = New-Object System.Drawing.Point(20,10)
        $label.Size = New-Object System.Drawing.Size(370,20)
        if($Multiselect -eq $true) {
            $label.Text = 'Select from the list below: (hold control to select multiple options)'
        } else {
            $label.Text = 'Select from the list below:'
        }
        $form.Controls.Add($label)
        
        $listBox = New-Object System.Windows.Forms.Listbox
        $ListHeight = $FormHeight-120
        $ListWidth = $FormWidth-56
        $listBox.Location = New-Object System.Drawing.Point(20,40)
        $listBox.Size = New-Object System.Drawing.Size($ListHeight,$ListWidth)
        
        if($Multiselect -eq $true) {
            $listBox.SelectionMode = 'MultiExtended'
        }
        
        foreach($tempSKU in $CIMInput)
        {
            [void] $listBox.Items.Add($tempSKU)
        }
        
        $listBox.Height = $ListHeight #70
        $listBox.Width = $ListWidth #70
        $form.Controls.Add($listBox)
        $form.Topmost = $true
        
        $result = $form.ShowDialog()
        
        if ($result -eq [System.Windows.Forms.DialogResult]::OK)
        {
            $x = $listBox.SelectedItems
        }
        else{
            $x = $null
        }

        return $x
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Output $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)
    }
}
}

# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
Function Set-WMIValue {
<#
This function is used to set a WMI property using CIM. If the namespace or class do not exist, it will be created.
 
All parameters can be pipelined by their respective propertyname
 
PARAMETERS:
-Namespace: Specifies the namespace where to search for the WMI class. Default is: 'ROOT\cimv2'.
-ClassName: Specifies the class where to create the new WMI instance.
-KeyID: (Optional) Specifies the property within the $Property hashtable that is used as the key. If an instance with the key exists, it will be updated. If it does not, it will be created as a new instance.
            If omitted, the current datetime and calling script will be used as the key value for creating a new instance using the key property "CT_Key"
-Property: Specifies the class instance Properties or Values.
            Example of hashtable:
            [hashtable]$Property = @{
                'ServerPort' = '89'
                'ServerIP' = '11.11.11.11'
                'Source' = 'File1'
                'Date' = $(Get-Date)
            }
 
 
EXAMPLES:
[hashtable]$Property = @{
    'ServerPort' = '89'
    'ServerIP' = '11.11.11.11'
    'ServerName' = 'File1'
    'Date' = $(Get-Date)
}
Set-WMIValue -Namespace "ROOT\cimv2" -ClassName "CORP_Servers" -KeyID "ServerName" -Property $Property
 
#>

[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Parameter(ValueFromPipelineByPropertyName,HelpMessage="WMI Namespace (root\CIMV2 if not specified)")]
    [ValidateNotNullorEmpty()]
    [string]$Namespace = 'ROOT\cimv2',
    [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Class Name")]
    [ValidateNotNullorEmpty()]
    [string]$ClassName,
    [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Key Property Name")]
    [ValidateNotNullorEmpty()]
    [string]$KeyID,
    [Parameter(ValueFromPipelineByPropertyName,HelpMessage="CIM property hashtable")]
    [ValidateNotNullorEmpty()]
    [hashtable] $Property
)

Process{

    try{

        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Set-WMIValue function triggered with the following parameters:
        Namespace: $($Namespace)
        ClassName: $($ClassName)
        KeyID: $($KeyID)
        KeyValue: $($KeyValue)
        Properties:
        $($Property | Out-String)
        "


        ### Check if the namespace exists and create if not
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Checking to see if Namespace '$($Namespace)' exists"

        $SplitNamespace = $Namespace.Split("\")
        if ($SplitNamespace[0] -notlike "root") {
            $line = Get-CurrentLineNumber
            Write-Verbose "Invalid namespace path '$($SplitNamespace[0])'. Namespace should always start with 'ROOT'." -ShowAsError -Line $line
            $PScmdlet.ThrowTerminatingError("ERROR: Invalid namespace path '$($SplitNamespace[0])'. Namespace should always start with 'ROOT'.")
        }
        $NamespaceTest = get-wmiobject -namespace $SplitNamespace[0] -Class __NAMESPACE -Filter "name='$($SplitNamespace[1])'" -Verbose 4>&1 >> $global:Verbose_log -ErrorAction 'SilentlyContinue'
# $NamespaceTest = Get-WmiNamespace -Namespace $Namespace -ErrorAction 'SilentlyContinue'

        If (!$NamespaceTest) {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "'$($Namespace)' does not exist - creating new Namespace"
            $NameSpaceObject = (New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList "\\.\$($SplitNamespace[0])`:__NAMESPACE").CreateInstance()
            $NameSpaceObject.Name = $SplitNamespace[1]

            # Write the namespace object
            $NewNamespace = $NameSpaceObject.Put()
            $NameSpaceObject.Dispose()
        }



        ### Check if the class exists and create if not
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Checking to see if Class '$($ClassName)' exists"
        $ClassTest = Get-CimClass -Namespace $Namespace -ClassName $ClassName  -Verbose 4>&1 >> $global:Verbose_log -ErrorAction 'SilentlyContinue'

        If (!$ClassTest) {

            try{
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "'$($ClassName)' does not exist - creating new Class"
                # Create class object
                [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:__CLASS", [String]::Empty, $null)
                $ClassObject.Name = $ClassName

                # Write the class and dispose of the class object
                $NewClass = $ClassObject.Put()
                $ClassObject.Dispose()

            } catch {
                # On class creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified

                # Error handling and logging
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Output "Failed to create class [$ClassName] in namespace [$Namespace]." -ShowAsError -Line $line
                Write-Output $_ -ShowAsError -Line $line
                $PScmdlet.ThrowTerminatingError($_)
            }

        }
        ### End class check and creation


        ### Check if key exists and if not, set

        # First check for or set the Key and its respective value
        if($KeyID) {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Checking to see if key '$($KeyID)' exists in provided properties"
            # Check if instance exists with the provided key
            try{
               $KeyValue = $Property[$KeyID]
            } catch {
                #Key doesn't exist in the hashtable, will switch to creating default one
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "$($KeyID) doesn't exist in the Property hashtable. Will use default key of 'CT_Key' and value '$($DateStamp)$($Global:Script_Name)'." -ShowAsWarning -Line $line
            }
        }
        if (-not $KeyValue) {
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Creating key 'CT_Key' and adding to provided properties"
            $KeyID = "CT_Key"
            $KeyValue = "$($DateStamp)$($Global:Script_Name)"
            $NewProperty = @{$KeyID = $KeyValue}
            $NewProperty += $Property
            $Property = $NewProperty
        }
        ### End key check





        ### If the Class is new, add all the properties to the Class
        If ($NewClass) {
            try{
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "Checking all provided properties exist in class '$($ClassName)' within namespace '$($Namespace)'."

                Write-Verbose "Adding key property '$($KeyID)'"

                [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$ClassName")
        
                # Add class property
                $PropertyIsArray = $false
                $PropertyType = switch (($Property[$KeyID].GetType()).Name) {
                    "Byte" { "UInt8"; break }
                    "UInt16" { "UInt16"; break }
                    "UInt32" { "UIUInt32nt8"; break }
                    "UInt64" { "UInt64"; break }
                    "SByte" { "SInt8"; break }
                    "Int16" { "SInt16"; break }
                    "Int32" { "SInt32"; break }
                    "Int" { "SInt32"; break }
                    "Int64" { "SInt64"; break }
                    "Single" { "Real32"; break }
                    "Double" { "Real64"; break }
                    "Boolean" { "Boolean"; break }
                    "DateTime" { "DateTime"; break }
                    "TimeSpan" { "DateTime"; break }
                    "Char" { "Char16"; break }
                    "String" { "String"; break }
                    "Array" { "Array"; break }
                Default {($Property[$KeyID].GetType()).Name}
                }
                
                
                $ClassObject.Properties.Add($KeyID, [System.Management.CimType]$PropertyType, $PropertyIsArray)
                $ClassObject.Properties[$KeyID].Qualifiers.Add('read',$true)
                $ClassObject.Properties[$KeyID].Qualifiers.Add('write',$true)
                $ClassObject.Properties[$KeyID].Qualifiers.Add('key',$true)

                # Write class object
                try {
                    $null = $ClassObject.Put()
                    $line = $_.InvocationInfo.ScriptLineNumber
                    Write-Verbose "Added property '$($KeyID)' to class '$($ClassName)' within namespace '$($Namespace)'."
                } catch {
                    $line = $_.InvocationInfo.ScriptLineNumber
                    Write-Verbose "Unable to create property '$($PropekeyrtyName)' in class '$($ClassName)' within namespace '$($Namespace)'." -ShowAsError -Line $line
                    Write-Verbose $_ -ShowAsError -Line $line
                    $PScmdlet.ThrowTerminatingError($_)
                }
                $ClassObject.Dispose()

                ForEach ($PropertyName in $Property.Keys | Where-Object {$_ -ne $KeyID}) {
                    $line = $_.InvocationInfo.ScriptLineNumber

                    # Set property to array if specified
                    $PropertyType = switch (($Property[$PropertyName].GetType()).Name) {
                        "Byte" { "UInt8"; break }
                        "UInt16" { "UInt16"; break }
                        "UInt32" { "UIUInt32nt8"; break }
                        "UInt64" { "UInt64"; break }
                        "SByte" { "SInt8"; break }
                        "Int16" { "SInt16"; break }
                        "Int32" { "SInt32"; break }
                        "Int" { "SInt32"; break }
                        "Int64" { "SInt64"; break }
                        "Single" { "Real32"; break }
                        "Double" { "Real64"; break }
                        "Boolean" { "Boolean"; break }
                        "DateTime" { "DateTime"; break }
                        "TimeSpan" { "DateTime"; break }
                        "Char" { "Char16"; break }
                        "String" { "String"; break }
                        "Array" { "Array"; break }
                        Default {($Property[$PropertyName].GetType()).Name}
                    }
                    If ($PropertyType -match 'Array') {
                        $PropertyType = $PropertyType.Replace('Array','')
                        $PropertyIsArray = $true
                    } Else {
                        $PropertyIsArray = $false
                        #$PropertyType = "String"
                    }
    
                    # Create the ManagementClass object
                    [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$ClassName")
    
                    # Add class property
                    $ClassObject.Properties.Add($PropertyName, [System.Management.CimType]$PropertyType, $PropertyIsArray)
                    $ClassObject.Properties[$PropertyName].Qualifiers.Add('read',$true)
                    $ClassObject.Properties[$PropertyName].Qualifiers.Add('read',$true)
                    $ClassObject.Properties[$PropertyName].Qualifiers.Add('write',$true)
    
                    # Write class object
                    try {
                        $null = $ClassObject.Put()
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose "Added property '$($PropertyName)' to class '$($ClassName)' within namespace '$($Namespace)'."
                    } catch {
                        $line = $_.InvocationInfo.ScriptLineNumber
                        Write-Verbose "Unable to create property '$($PropertyName)' in class '$($ClassName)' within namespace '$($Namespace)'." -ShowAsError -Line $line
                        Write-Verbose $_ -ShowAsError -Line $line
                        Write-Verbose "Removing property '$($PropertyName)' from the list of Properties to send to WMI class '$($ClassName)' within namespace '$($Namespace)'."
                        $Property.Remove($PropertyName)
                    }
                    $ClassObject.Dispose()
                }
            } catch {
                ## Catch any errors and log
                $line = $_.InvocationInfo.ScriptLineNumber
                Write-Verbose "Unable to add properties to existing class '$($ClassName)' within namespace '$($Namespace)'." -ShowAsError -Line $line
                Write-Verbose $_ -ShowAsError -Line $line
                $PScmdlet.ThrowTerminatingError($_)
            }
        }
        ### End property setup





        ### Check if instance exists using key/value pair and create if not
        $line = $_.InvocationInfo.ScriptLineNumber
        $CIMQuery = "Select * from $($ClassName) where $($KeyID) LIKE '$($KeyValue)'"
        Write-Verbose "Checking if an instance with key value '$($KeyValue)' exists in class '$($ClassName)' within namespace '$($Namespace)'."
        Write-Verbose "WMI Query: $($CIMQuery)."

        $InstanceTest = Get-CimInstance -Namespace $Namespace -Query $CIMQuery -ErrorAction 'SilentlyContinue' -Verbose 4>&1 >> $global:Verbose_log
        if (!$InstanceTest) {
            #Instance doesn't exist so it needs to be created. create it with its properties
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Instance does not exist with key value '$($KeyValue)' in class '$($ClassName)' within namespace '$($Namespace)'."
            Write-Verbose "Adding a new instance."
            $Instance = New-CimInstance -Namespace $Namespace -ClassName $ClassName -Key $KeyID -Property $Property -Verbose 4>&1 >> $global:Verbose_log
        } else {
            #Instance exists so update it with new properties
            $line = $_.InvocationInfo.ScriptLineNumber
            Write-Verbose "Instance exist with key value '$($KeyValue)' in class '$($ClassName)' within namespace '$($Namespace)'."
            Write-Verbose "Updating existing instance."
            $Instance = Set-CimInstance -Namespace $Namespace -Query $CIMQuery -Property $Property -PassThru -Verbose 4>&1 >> $global:Verbose_log
        }


    } catch {
        ## Catch any errors and log
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Unable to populate WMI Values in class '$($ClassName)' within namespace '$($Namespace)'." -ShowAsWarning -Line $line
        Write-Verbose $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)

    }
}
}
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
# This function returns $True if the script is running interactively, or $False if it is not
function Test-InteractiveMode {
[CmdletBinding(SupportsShouldProcess=$true)]
param()

Process{

    try{
        # Test parent script Args for '-Interactive' switch.
        $InteractiveMode = ($PSCmdlet.MyInvocation.BoundParameters["Interactive"].IsPresent -eq $true)
        #$InteractiveMode = [Environment]::GetCommandLineArgs() | Where-Object{ $_ -like '-Interactive*' }

        if ([Environment]::UserInteractive -and -not $InteractiveMode) {
            # We are in an interactive shell.
            return $true
        } else {
            return $false
        }
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Unable to determine if script is running interactively or not." -ShowAsError -Line $line
        Write-Verbose $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)
    }
}
}
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
function Get-TLS12Status {
[CmdletBinding(SupportsShouldProcess=$true)]
param ()

Begin {
    # Create the array of registry hashtables for TLS1.2
    $TLSvalues = @(
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"
            RegKey = "DisabledByDefault"
            RegKeyType = "DWORD"
            RegKeyValue = "0"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"
            RegKey = "Enabled"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
            RegKey = "DisabledByDefault"
            RegKeyType = "DWORD"
            RegKeyValue = "0"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
            RegKey = "Enabled"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
            RegKey = "DefaultSecureProtocols"
            RegKeyType = "DWORD"
            RegKeyValue = "2048"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
            RegKey = "DefaultSecureProtocols"
            RegKeyType = "DWORD"
            RegKeyValue = "2048"
            x64 = $true
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319"
            RegKey = "SchUseStrongCrypto"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319"
            RegKey = "SchUseStrongCrypto"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $true
        }
    )
}

Process {
    # --------------------------------------------------------------------------------------------------
    # Check registry keys for TLS1.2 settings and self-heal if -Heal specified
    Write-Verbose "Checking TLS 1.2 is enabled"
    $TLSStatus = $true
    try {
        foreach($regcheck in $TLSvalues) {
            #Write-Verbose "[L$(Get-CurrentLineNumber)] Checking for TLS1.2 registry path '$($regcheck.RegPath)'"
            if (($regcheck.x64 -ne $true) -or ([Environment]::Is64BitProcess -and $regcheck.x64 -eq $true)) {
                if(!(Test-Path -Path $regcheck.RegPath)) {
                    $regpathParent = ($regcheck.RegPath | Split-Path -Parent)
                    $regpathLeaf = ($regcheck.RegPath | Split-Path -Leaf)
                    $TLSStatus = $false
                } else {
                    #Write-Verbose "[L$(Get-CurrentLineNumber)] TLS1.2 Registry path '$($regcheck.RegPath)' exists."
                }
                #Write-Verbose "[L$(Get-CurrentLineNumber)] Checking for TLS1.2 registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)'"
                $keycheck = Get-ItemProperty -Path $regcheck.RegPath -Name $regcheck.RegKey -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                if(!$keycheck) {
                    Write-Verbose "TLS1.2 Registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' not found"
                    $TLSStatus = $false
                }else{
                    $valuecheck = Get-ItemPropertyValue -Path $regcheck.RegPath -Name $regcheck.RegKey -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                    #Write-Verbose "[L$(Get-CurrentLineNumber)] Comparing value '$($valuecheck)' of TLS1.2 registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' against correct value '$($regcheck.RegKeyValue)' required for TLS1.2"
                    if ($valuecheck -ne $regcheck.RegKeyValue) {
                        $TLSStatus = $false
                    } else {
                        #Write-Verbose "[L$(Get-CurrentLineNumber)] TLS1.2 value '$($valuecheck)' of registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' matches '$($regcheck.RegKeyValue)'"
                    }

                }
            }
        }
        return $TLSStatus
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Unable to check TLS 1.2 status." -ShowAsError -Line $line
        Write-Verbose $_ -ShowAsError -Line $line
        $TLSStatus = $false
        $PScmdlet.ThrowTerminatingError($_)
    }

}

}

# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
function Set-TLS12 {
[CmdletBinding(SupportsShouldProcess=$true)]
param ()

Begin {
    # Create the array of registry hashtables for TLS1.2
    $TLSvalues = @(
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"
            RegKey = "DisabledByDefault"
            RegKeyType = "DWORD"
            RegKeyValue = "0"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"
            RegKey = "Enabled"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
            RegKey = "DisabledByDefault"
            RegKeyType = "DWORD"
            RegKeyValue = "0"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
            RegKey = "Enabled"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
            RegKey = "DefaultSecureProtocols"
            RegKeyType = "DWORD"
            RegKeyValue = "2048"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
            RegKey = "DefaultSecureProtocols"
            RegKeyType = "DWORD"
            RegKeyValue = "2048"
            x64 = $true
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319"
            RegKey = "SchUseStrongCrypto"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $false
        },
        @{
            RegPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319"
            RegKey = "SchUseStrongCrypto"
            RegKeyType = "DWORD"
            RegKeyValue = "1"
            x64 = $true
        }
    )
}

Process {
    # --------------------------------------------------------------------------------------------------
    # Check registry keys for TLS1.2 settings and self-heal if -Heal specified
    Write-Verbose "Checking TLS 1.2 is enabled"
    try {
        foreach($regcheck in $TLSvalues) {
            #Write-Verbose "[L$(Get-CurrentLineNumber)] Checking for TLS1.2 registry path '$($regcheck.RegPath)'"
            if (($regcheck.x64 -ne $true) -or ([Environment]::Is64BitProcess -and $regcheck.x64 -eq $true)) {
                if(!(Test-Path -Path $regcheck.RegPath)) {
                    $regpathParent = ($regcheck.RegPath | Split-Path -Parent)
                    $regpathLeaf = ($regcheck.RegPath | Split-Path -Leaf)
                    New-Item -Path $regpathParent -Name $regpathLeaf -Force -Verbose 4>&1 >> $global:Verbose_log | Out-Null
                } else {
                    #Write-Verbose "[L$(Get-CurrentLineNumber)] TLS1.2 Registry path '$($regcheck.RegPath)' exists."
                }
                #Write-Verbose "[L$(Get-CurrentLineNumber)] Checking for TLS1.2 registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)'"
                $keycheck = Get-ItemProperty -Path $regcheck.RegPath -Name $regcheck.RegKey -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                if(!$keycheck) {
                    Write-Verbose "TLS1.2 Registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' not found, creating"
                    Set-ItemProperty -Path $regcheck.RegPath -Name $regcheck.RegKey -Value $regcheck.RegKeyValue -Type $regcheck.RegKeyType -Verbose 4>&1 >> $global:Verbose_log
                    try {
                        New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name "RebootRequired" -Force -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log | Out-Null
                        Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -Name "2f1d0398-6306-4e5f-b24b-7a45e59eb3bc" -Value "1" -Type Dword -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                    } catch {
                        #Couldn't set reboot required
                    }
                }else{
                    $valuecheck = Get-ItemPropertyValue -Path $regcheck.RegPath -Name $regcheck.RegKey -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                    #Write-Verbose "[L$(Get-CurrentLineNumber)] Comparing value '$($valuecheck)' of TLS1.2 registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' against correct value '$($regcheck.RegKeyValue)' required for TLS1.2"
                    if ($valuecheck -ne $regcheck.RegKeyValue) {
                        Set-ItemProperty -Path $regcheck.RegPath -Name $regcheck.RegKey -Value $regcheck.RegKeyValue -Type $regcheck.RegKeyType -Verbose 4>&1 >> $global:Verbose_log
                        try {
                            New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name "RebootRequired" -Force -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log | Out-Null
                            Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -Name "2f1d0398-6306-4e5f-b24b-7a45e59eb3bc" -Value "1" -Type Dword -ErrorAction SilentlyContinue -Verbose 4>&1 >> $global:Verbose_log
                        } catch {
                            #Couldn't set reboot required
                        }
                    } else {
                        #Write-Verbose "[L$(Get-CurrentLineNumber)] TLS1.2 value '$($valuecheck)' of registry key '$($regcheck.RegKey)' at path '$($regcheck.RegPath)' matches '$($regcheck.RegKeyValue)'"
                    }

                }
            }
        }
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Verbose "Unable to enable TLS 1.2." -ShowAsError -Line $line
        Write-Verbose $_ -ShowAsError -Line $line
        $PScmdlet.ThrowTerminatingError($_)
    }

}

}

# --------------------------------------------------------------------------------------------------