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, 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. Modules are imported IN-ORDER of appearance in the array. ***************** $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 run the PS 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="$($env:ProgramData)\Centorrino_Technologies" # 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 if($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true -or $global:VerbosePreference -ne "SilentlyContinue") { $PSDefaultParameterValues['*:Verbose'] = $true # Sets the verbosity for all commands } #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 = $global:CT_DEST $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 "$($global:CT_DEST)\$($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 = "$($global:CT_DEST)\logs\$($Global:Script_Name)\$($global:DateStamp)_output.log" # The output $global:Transcript_log = "$($global:CT_DEST)\logs\$($Global:Script_Name)\$($global:DateStamp)_transcript.log" # The powershell transcript file $global:Verbose_log = "$($global:CT_DEST)\logs\$($Global:Script_Name)\$($global:DateStamp)_verbose.log" # The output $global:API_log = "$($($global:CT_DEST))\logs\$($Global:Script_Name)\$($global:DateStamp)_API.log" $global:Install_log = "$($global: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 $global:CT_DEST )) { try{ mkdir $global:CT_DEST > $null #Transcript-Log "New folder created at $global:CT_DEST." }catch{ #Can't create the folder, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [ML$($line)] Cannot create folder $($global:CT_DEST).`n$($_)" -Category WriteError -CategoryReason "Cannot create folder $($global: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 "$($global:CT_DEST)\logs" )) { try{ mkdir "$($global:CT_DEST)\logs" > $null #Transcript-Log "New logs folder created at $global:CT_DEST." }catch{ #Can't create the folder, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [ML$($line)] Cannot create logs folder in $($global:CT_DEST).`n$($_)" -Category WriteError -CategoryReason "Cannot create logs folder in $($global: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 "$($global:CT_DEST)\logs\$($Global:Script_Name)" )) { try{ mkdir "$($global:CT_DEST)\logs\$($Global:Script_Name)" > $null #Transcript-Log "New logs folder for $($Global:Script_Name) created at $global: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 $($global:CT_DEST).`n$($_)" -Category WriteError -CategoryReason "Cannot create logs folder for $($Global:Script_Name) in $($global:CT_DEST)." -ErrorId "1004" -ErrorAction Continue Write-Error $_ -ErrorAction Continue #Stop-Transcript $exiterror = 1004 throw $exiterror throw } } <# No longer using registry or WMI - using files now # 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).`n$($_)" -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 "$($global:CT_DEST)\nuget.lck" )) { "$($DateStamp) lock" > "$($global: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 "$($global: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 "$($global:CT_DEST)\psget.lck" )) { "$($DateStamp) lock" > "$($global: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 "$($global: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 : $($global: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 set the script lock file # This function is used in conjunction with Test-ScriptLock and Clear-ScriptLock to prevent the same script running more than once consecutively Function Set-ScriptLock { [CmdletBinding()] Param( [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 = "$($global:CT_DEST)\$($LockFile)" "$($global:DateStamp) lock" > $LockFile } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Output "Cannot set lock file '$($LockFile)'." -ShowAsError -Line $line Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # This is used to clear the script lock file # This function is used in conjunction with Test-ScriptLock and Set-ScriptLock to prevent the same script running more than once consecutively Function Clear-ScriptLock { [CmdletBinding()] Param( [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 = "$($global:CT_DEST)\$($LockFile)" if(Test-Path -Path $LockFile) { Remove-Item -Path $LockFile } else { Write-Verbose "No lock file '$($LockFile)' found." -ShowAsWarning } } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Output "Cannot remove lock file '$($LockFile)'." -ShowAsError -Line $line Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # 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( [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 = "$($global:CT_DEST)\$($LockFile)" Write-Verbose "Checking for script lock file at '$($LockFile)'" if(Test-Path -Path $LockFile) { return $true } else { # No lock file found 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 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 = "$($global:CT_DEST)\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 = "$($global:CT_DEST)\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 function write a value to a key (stored as a file) function Set-Value { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Value Key")] [ValidateNotNullorEmpty()] [string]$Key, [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Value as psobject")] [ValidateNotNullorEmpty()] [psobject]$Value, [switch]$Append ) Process{ try { Write-Verbose "Set-Value function triggered with the following parameters:`n Key: $($Key)`n Value: $($Value | Out-String)`n" $ValFile = "$($global:CT_DEST)\$($Global:Script_Name)\$($Key).cfg" $OutString = "" if($Value.GetType().Name -eq "Hashtable"){ $Value.GetEnumerator() | ForEach-Object{ $OutString += "`t$($_.key)=$($_.value)`n" } } else { $OutString = "$($Value | Out-String)" } $OutString = $OutString.Trim() if ($Append) { "$($OutString)" >> $ValFile } else { "$($OutString)" > $ValFile } } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Output "Cannot write to file '$($ValFile)'." -ShowAsError -Line $line Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # This function write a value to a key (stored as a file) function Get-Value { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Value Key")] [ValidateNotNullorEmpty()] [string]$Key ) Process{ try { Write-Verbose "Get-Value function triggered with the following parameters:`n Key: $($Key)`n" $ValFile = "$($global:CT_DEST)\$($Global:Script_Name)\$($Key).cfg" $GotValue = Get-Content $ValFile Write-Verbose "Read $($GotValue.count) value(s)" $ExtraHashtable = @{} if($GotValue.count -gt 1) { $ReturnValue = @() } else {$ReturnValue = $null} foreach($Value in $GotValue) { if($Value) { if($Value.Substring(0,1) -eq "`t") { #Value belongs to a hashtable $ValSplit = $Value.Split("=") try { $ExtraHashtable.add($ValSplit[0].trim(),$ValSplit[1]) } catch { $ExtraHashtable[$ValSplit[0].trim()] = $ValSplit[1] } } else { #Value is not a hashtable $ReturnValue += $Value } } } if($GotValue.count -le 1) {$ReturnValue = $ReturnValue.Trim()} if($ExtraHashtable -and $ReturnValue) { $ReturnValue += $ExtraHashtable } elseif ($ExtraHashtable -and !$ReturnValue) { $ReturnValue = $ExtraHashtable } #Write-Verbose "Read $($Value.count) values" return $ReturnValue } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Output "Cannot read from file '$($ValFile)'." -ShowAsError -Line $line Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # This function clears a value stored in a key (stored as a file). The value can be specified to just clear that portion of the key value. function Clear-Value { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Value Key")] [ValidateNotNullorEmpty()] [string]$Key, [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Value as psobject")] [ValidateNotNullorEmpty()] [psobject]$Value ) Process{ try { Write-Verbose "Clear-Value function triggered with the following parameters:`n Key: $($Key)`n Value: $($Value | Out-String)`n" $ValFile = "$($global:CT_DEST)\$($Global:Script_Name)\$($Key).cfg" } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Output "Cannot clear value in file '$($ValFile)'." -ShowAsError -Line $line Write-Error "ERROR: [ML$($line)] `n $($_)" -ErrorAction Continue $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($_) } } } # -------------------------------------------------------------------------------------------------- |