CT-PS-Standard.psm1
<#
-------------------------------------------------------------------------------------------------- This is a standard module with a set of standard functions used across multiple scripts within CT. Any changes to this module need to be published using the powershell script "Build_CT_Module.ps1" -------------------------------------------------------------------------------------------------- HOW TO IMPORT INTO SCRIPT: -------------------------------------------------------------------------------------------------- This module should be imported using the commands below (do not copy the asterix's, just whats between). This will import this module AND initialise the script with all the standard features required by scripts, including all the log files for each transaction ***************** [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Register-PSRepository -Default -ErrorAction SilentlyContinue | Out-Null Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue -Verbose:$VerbosePreference | Out-Null Install-Module -Name "CT-PS-Standard" -Force -AllowClobber -Scope CurrentUser -Verbose:$VerbosePreference -ErrorAction Stop Import-Module -Name "CT-PS-Standard" -Force -Verbose:$VerbosePreference -ErrorAction Stop ***************** If this does not work or fails, use the code below. Sometimes if the standard module has been imported globally and not released, it can create locking issues, so it needs removal first. ***************** [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($null -eq (Get-PSRepository)) { Register-PSRepository -Default -Verbose:$VerbosePreference } Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue -Verbose:$VerbosePreference Remove-Module -Name "CT-PS-Standard" -Force -Verbose:$VerbosePreference -ErrorAction SilentlyContinue Uninstall-Module -Name "CT-PS-Standard" -AllVersions -Force -ErrorAction SilentlyContinue -Verbose:$VerbosePreference Install-Module -Name "CT-PS-Standard" -Force -AllowClobber -Scope CurrentUser -Verbose:$VerbosePreference -ErrorAction Stop Import-Module -Name "CT-PS-Standard" -Force -Verbose:$VerbosePreference -ErrorAction Stop ***************** -------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------- 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 #Begin { # Display some troubleshoot info [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-verbose "MyInvocation Info: $($MyInvocation | Select-Object -Property * | Out-String) BoundParameters: $($MyInvocation.BoundParameters | Select-Object -Property * | Out-String) UnboundArguments: $($MyInvocation.UnboundArguments | Select-Object -Property * | Out-String) " Write-verbose "Global MyInvocation Info: $($global:MyInvocation | Select-Object -Property * | Out-String) BoundParameters: $($global:MyInvocation.BoundParameters | Select-Object -Property * | Out-String) UnboundArguments: $($global:MyInvocation.UnboundArguments | Select-Object -Property * | Out-String) " Write-verbose "Current automatic variables: ScriptName: $($ScriptName) PSScriptRoot: $($PSScriptRoot) PSCommandPath: $($PSCommandPath) " Write-verbose "Current global variables: ScriptName: $($global:ScriptName) PSScriptRoot: $($global:PSScriptRoot) PSCommandPath: $($global:PSCommandPath) " $global:ErrorActionPreference = "Stop" $global:CT_DEST="C:\CT" # Where the files are downloaded to $global:DateStamp = get-date -Format yyyyMMddTHHmmss # A formatted date strong try { $global:Script_Path = Split-Path $global:PSCommandPath -Parent if ($global:PSCommandPath -gt 4) { try { $Global:Script_Name = Split-Path $global:PSCommandPath -Leaf $Global:Script_Name = ($Global:Script_Name).Substring(0,($Global:Script_Name).Length-4) } catch { $Global:Script_Name = "Other_$(get-date -Format yyyyMMdd)" } } else { $Global:Script_Name = "Terminal_$(get-date -Format yyyyMMdd)" } } catch { $global:Script_Path = "C:\CT" $Global:Script_Name = "Terminal_$(get-date -Format yyyyMMdd)" } $global:Output_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_output.log" # The output $global:Transcript_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_transcript.log" # The powershell transcript file $global:API_log = "$($CT_DEST)\logs\$($Script_Name)\$($DateStamp)_API.log" $global:Install_log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_install.log" # The powershell installation file #} #End begin block #Process { # 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-host "There is a problem with this computer and updates are required for this script to continue." $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [L$($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 Stop #Stop-Transcript | Out-Null $exiterror = 1001 throw "There is a problem with this computer and updates are required for this script to continue." exit $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: [L$($line)] Cannot create folder $CT_DEST. $($_)" -Category WriteError -CategoryReason "Cannot create folder $($CT_DEST)." -ErrorId "1002" -ErrorAction Stop Write-Error $_ -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) -ErrorAction Continue #Stop-Transcript $exiterror = 1002 exit $exiterror } } 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: [L$($line)] Cannot create logs folder in $CT_DEST. $($_)" -Category WriteError -CategoryReason "Cannot create logs folder in $($CT_DEST)." -ErrorId "1003" -ErrorAction Stop Write-Error $_ -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) -ErrorAction Continue #Stop-Transcript $exiterror = 1003 exit $exiterror } } if(-not( Test-Path -Path "$($CT_DEST)\logs\$($Script_Name)" )) { try{ mkdir "$($CT_DEST)\logs\$($Script_Name)" > $null #Transcript-Log "New logs folder for $($Script_Name) created at $CT_DEST." }catch{ #Can't create the folder, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [L$($line)] Cannot create logs folder for $($Script_Name) in $($CT_DEST). $($_)" -Category WriteError -CategoryReason "Cannot create logs folder for $($Script_Name) in $($CT_DEST)." -ErrorId "1004" -ErrorAction Stop Write-Error $_ -ErrorAction Continue #Stop-Transcript throw $exiterror = 1004 exit $exiterror } } $global:CT_Reg_Path = "HKLM:\Software\CT\Monitoring" $global:CT_Reg_Key = "$($CT_Reg_Path)\$($Script_Name)" if(-not( Test-Path -Path $CT_Reg_Key )) { try{ $CTMonitoringReg = New-Item -Path $CT_Reg_Path -Name $Script_Name -Force Set-ItemProperty -Path "HKLM:\Software\CT" -Name "CustomerNo" -Value $customer }catch{ #Can't create the regkey, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [L$($line)] Cannot create registry key at $($CT_Reg_Key). $($_)" -ErrorId "1005" -Category WriteError -CategoryReason "Cannot create registry key $($CT_Reg_Path)." -ErrorAction Stop Write-Error "$($CTMonitoringReg)" -ErrorAction Continue Write-Error $_ #Stop-Transcript $exiterror = 1005 exit $exiterror } } #Setup TLS 1.1 and 1.2 $Name = 'DisabledByDefault' $Value = '0' if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" )) { try{ New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.1" -Force | Out-Null New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1" -Name "Client" -Force | Out-Null New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null }catch{ #Can't create the regkey, therefore cannot continue Write-Error "Cannot set TLS 1.1." -ErrorAction Continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [L$($line)] Cannot set TLS 1.1. $($_)" -ErrorId "1006" -Category WriteError -CategoryReason "Cannot set TLS 1.1." -ErrorAction Stop Write-Error $_ } } else { Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null } if(-not( Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" )) { try{ New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Name "TLS 1.2" -Force | Out-Null New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" -Name "Client" -Force | Out-Null New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null }catch{ #Can't create the regkey, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber write-Error "ERROR: [L$($line)] Cannot set TLS 1.2. $($_)" -ErrorId "1007" -Category WriteError -CategoryReason "Cannot set TLS 1.2." -ErrorAction Stop Write-Error $_ } } else { Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client" -Name $Name -Value $Value | Out-Null } # Create a Transcript header for the verbose log Write-Verbose "**********************" Write-Verbose "Script: $($Script_Name)." Write-Verbose "Start time: $($DateStamp)" Write-Verbose "Username: $($env:USERDOMAIN)\$($env:USERNAME)" Write-Verbose "Execution Policy Preference: $($env:PSExecutionPolicyPreference)" Write-Verbose "Machine: $($env:COMPUTERNAME) ($($env:OS))" Write-Verbose "Process ID: $($PID)" Write-Verbose "PSVersion: $($PSVersionTable.PSVersion)" Write-Verbose "PSEdition: $($PSVersionTable.PSEdition)" Write-Verbose "Operating System: $($PSVersionTable.OS)" Write-Verbose "WSManStackVersion: $($PSVersionTable.WSManStackVersion)" Write-Verbose "PSRemotingProtocolVersion: $($PSVersionTable.PSRemotingProtocolVersion)" Write-Verbose "SerializationVersion: $($PSVersionTable.SerializationVersion)" Write-Verbose "**********************" <# ---- END STANDARD SCRIPT BLOCK---- #> } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Error "ERROR: [L$($line)] $($_)" -ErrorAction Stop Throw } #} #End process block # This ends the script block that run on module import # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # Initialize-Script is the old command for when the module is imported, its kept in here for backwards compatibility but it now only calls the import-module command function Initialize-Script { [CmdletBinding()] param() Process{ if($null -eq $global:Output_log) { Import-Module CT-PS-Standard -Force -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) } else { Write-Host "Standard Module already imported - cannot import twice! Please check script $($Global:Script_Name) for import/init conflict. Script can continue however." } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # This is used to retrieve 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 write output to an output log file without writing it to the console (to keep console output clean) unless -Verbose is specified Function Write-OutputLog { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(Mandatory,ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] $output, [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [switch]$IncludeConsole, [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [switch]$ForceLineNumber ) Process{ $outputstring = $output if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true) { Write-Verbose "(Output Log - Line $($MyInvocation.ScriptLineNumber)): $($output)" } if ($ForceLineNumber) { $outputstring = "[L$($MyInvocation.ScriptLineNumber)] $($output)" } try { $outputstring | Out-File -FilePath "$($global:Output_log)" -Append if($IncludeConsole -and ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -ne $true)) {Write-Host $outputstring} } catch { Write-Host "WARNING - Could not write to Output log file: $($outputstring)" } #Write-Host $output } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # Writes to the API log and optionally console if -Verbose flag is set at script level Function Write-APILog { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] $output, [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [switch]$IncludeConsole, [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [switch]$ForceLineNumber ) Process{ $outputstring = $output if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true) { Write-Verbose "(API Log - Line $($MyInvocation.ScriptLineNumber)): $($output)" } if ($ForceLineNumber) { $outputstring = "[L$($MyInvocation.ScriptLineNumber)] $($output)" } try { $outputstring | Out-File -FilePath "$($global:API_log)" -Append if($IncludeConsole -and ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -ne $true)) {Write-Host $outputstring} } catch { Write-Host "WARNING - Could not write to API log file: $($outputstring)" } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # 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 ) Process{ 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 -PathType Leaf ) { try { Remove-Item $FILE_LOCAL -Force } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot remove $($FILE_LOCAL)." $PScmdlet.ThrowTerminatingError($_) } } try { if ($ComputerType -ne 1) { Install-WindowsFeature BranchCache | Out-Null } } catch { $NoBITS = $true $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot install BranchCache." Write-Host $_ } if (!(Get-Module -ListAvailable -Name "BitsTransfer") -and !($NoBITS)) { try{ Import-Module BitsTransfer -Force } catch { $NoBITS = $true $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot install BitsTranfer." Write-Host $_ } } if (!($NoBITS)) { # Check if BranchCache Distributed Mode is enabled, and if not, enable it so BITS uses computers on the subnet to download where available $BCStatus = Get-BCStatus if ($BCStatus.ClientConfiguration.CurrentClientMode -ne "DistributedCache") { try { Enable-BCDistributed -Verbose -Force Write-Verbose "BranchCache Distributed Mode is now enabled" } catch { #BranchCache cannot be enabled to work with BITS. BITS will download over the internet connection instead of cached copies on the local subnet #Write-OutputLog "Cannot enable BranchCache Distributed Mode. $($_). The installation files will download over the internet connection instead of cached copies on the local subnet" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot enable BranchCache Distributed Mode." Write-Host $_ } } else { Write-Verbose "BranchCache Distributed Mode is already enabled in distributed mode on this computer" } try { if ($Credentials) { $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)" -Credential $Credentials } else { $DownloadJob = Start-BitsTransfer -Priority Normal -DisplayName "$($DateStamp) $($FILE_LOCAL)" -Source "$($FILE_URL)" -Destination "$($FILE_LOCAL)" } #Complete-BitsTransfer -BitsJob $DownloadJob Write-Verbose "Downloaded $($FILE_URL) using BITS to $($FILE_LOCAL)" # -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download $($FILE_URL) using BITS. Using standard web request." #Write-OutputLog "Cannot download $($FILE_URL) using BITS. Now trying through standard HTTP request." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) #Write-OutputLog "$($_ | Out-String)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) try { if ($Credentials) { $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -Credential $Credentials -UseBasicParsing } else { $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -UseBasicParsing } } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download $($FILE_URL) using standard web request." #Write-OutputLog "Cannot download $($FILE_URL) using standard HTTP request." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) #Write-OutputLog "$($_ | Out-String)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $PScmdlet.ThrowTerminatingError($_) } } } else { try { if ($Credentials) { $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -Credential $Credentials -UseBasicParsing } else { $DownloadJob = Invoke-WebRequest -Uri "$($FILE_URL)" -OutFile "$($FILE_LOCAL)" -PassThru -UseBasicParsing } } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download $($FILE_URL) using standard HTTP request." Write-Host $_ #Write-OutputLog "Cannot download $($FILE_URL) using standard HTTP request." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) #Write-OutputLog "$($_ | Out-String)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $PScmdlet.ThrowTerminatingError($_) } } return $DownloadJob } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # Returns current script line number function Get-CurrentLineNumber { [CmdletBinding()] 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." Write-Verbose "Powershell version $($PSVersionTable.PSVersion.ToString()) found." [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($dotnetversion -lt 379893) { Write-Verbose "Updating DotNet Framework to 4.5.2" $dotnet_URL = "https://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe" $dotnet_File = "C:\CT\NDP452-KB2901907-x86-x64-AllOS-ENU.exe" try { Invoke-WebRequest -Uri $dotnet_URL -OutFile $dotnet_File -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) -UseBasicParsing } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download DotNet Framework 4.5.2. $($_)" -ErrorId $_.Exception.HResult -Category ConnectionError -ErrorAction Continue #write-host "Cannot download DotNet Framework 4.5.2. $_" $PScmdlet.ThrowTerminatingError($_) } Write-Verbose "Installing DotNet Framework 4.5.2" try { $DotNetInstall = Start-Process -FilePath $dotnet_File -ArgumentList "/q /norestart" -Wait -NoNewWindow -PassThru -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) } 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-host "Unable to install DotNet Framework 4.5.2. Error code $($DotNetInstall.ExitCode) - $_" Write-Host "ERROR: [L$($line)] Unable to install DotNet Framework 4.5.2. $($_)" -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:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) #write-host "Found old WMF update and removed." } catch { #Can't remove the WMF, therefore cannot continue $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot remove $($WMF_File). $($_)" -ErrorId $_.Exception.HResult -ErrorAction Continue #write-host "Cannot remove $WMF_File. Unable to continue. $($Error[0].Exception.Message)" #Stop-Transcript $PScmdlet.ThrowTerminatingError($_) } } # Download WMF try { Invoke-WebRequest -Uri $WMF_URL -OutFile $WMF_File -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) -UseBasicParsing } catch { #Write-Verbose "Cannot download WMF. $($WMFjob.ErrorContextDescription)" $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download WMF. $($_)" -ErrorId $_.Exception.HResult -ErrorAction Continue #Stop-Transcript $PScmdlet.ThrowTerminatingError($_) } write-host "Installing WMF" $WMFUpgrade = Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList "$($WMF_File) /quiet /norestart" -Wait -NoNewWindow -PassThru -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) if (@(0,3010) -contains $WMFUpgrade.ExitCode) { Write-Verbose "WMF installed successfully. A reboot of this computer is required to complete the installation." } else { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Cannot download WMF. $($_)" -ErrorId $WMFUpgrade.ExitCode -ErrorAction Continue #write-host "Unable to install WMF. Error code $($WMFUpgrade.ExitCode)" #Stop-Transcript $PScmdlet.ThrowTerminatingError($_) } } if ($ForceReboot) { Start-Sleep -Seconds 60 Restart-Computer -Force -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) } 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-Host "DotNet Framework version $($dotnetversion) found." Write-Host "Powershell version $($PSVersionTable.PSVersion.ToString()) found." [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($dotnetversion -lt 379893) { $WMFupgrade = $true } if($OSBuild -eq "9600" -and $PowerShellVersion -lt 5.1) { # Windows 8.1 and Windows Server 2012r2 $WMFupgrade = $true } elseif($OSBuild -eq "9200" -and $PowerShellVersion -lt 5.1) { # Windows 8.1 and Windows Server 2012r2 $WMFupgrade = $true } if($WMFupgrade -eq $true) { try { Update-WMF } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Failed to upgrade WMF to 5.1. Please install DotNet Framework 4.5.2 and WMF 5.1 before upgrading PowerShell. $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue write-host "Failed to upgrade WMF to 5.1. Please install DotNet Framework 4.5.2 and WMF 5.1 before upgrading PowerShell" $PScmdlet.ThrowTerminatingError($_) } } Write-Host "Now will attempt to install latest PowerShell version alongside Windows PowerShell 5.1." try { Invoke-Expression -Command "& { $(Invoke-RestMethod -Uri 'https://aka.ms/install-powershell.ps1') } -UseMSI -Quiet" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) #iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI -Quiet" } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] Unable to install latest PowerShell. $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue Write-Host "Unable to install latest PowerShell" Write-Host $_ $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-OutputLog "Posting $($PostData.Count) items to API" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) Write-APILog "$($PostData | Out-String)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $nullfound = $false foreach ($APIData in $PostData) { #Write-OutputLog $member.name foreach ($APIentry in $APIData.GetEnumerator()) { Write-Host "$($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:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) if ($null -ne $body) { $retryCount = $Retry # performs a retry after 60 seconds if it fails do{ try { $SendToAPI = Invoke-WebRequest -URI "$($BASE_URL)$($EndPoint_URL)" -Method 'POST' -Headers $headers -Body $body -PassThru -Debug:$DebugPreference $retryCount = 0 #$ReturnValue = $SendToAPI } catch { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "ERROR: [L$($line)] $($_.Exception.Message). $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue Write-OutputLog $_.Exception.Message -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) Write-OutputLog $_.Exception -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) Write-OutputLog $SendToAPI -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) Write-OutputLog $body -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) # Dig into the exception to get the Response details. $line = $_.InvocationInfo.ScriptLineNumber Write-Error "($($line)) API Error $($SendToAPI.StatusCode): [$($_.Exception.Response.StatusCode.value__)] - $($_.Exception.Response.StatusDescription). $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue Write-OutputLog "API Error: $($SendToAPI.StatusCode) [$($_.Exception.Response.StatusCode.value__)] - $($_.Exception.Response.StatusDescription)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $line = $_.InvocationInfo.ScriptLineNumber #if ($DebugPreference -eq "Continue") { write-error "API error: $($_.Exception.Response.StatusCode.value__) - $($_.Exception.Response.StatusDescription)" -Category InvalidData -ErrorAction Continue } #$exiterror = $_.Exception.Response.StatusCode.value__ Set-ItemProperty -Path "$($CT_Reg_Key)" -Name "API-Post-$($BASE_URL)$($EndPoint_URL)" -Value "$($_.Exception.Response.StatusCode.value__)" start-sleep -Seconds 60 $retryCount = $retryCount - 1 if($retryCount -lt 1) {$PScmdlet.ThrowTerminatingError($_)} } } while ($retryCount -gt 0) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- # 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 ] Starting: $($MyInvocation.Mycommand)" #$CTPSModules = (Get-Module CT-PS-Standard -ListAvailable) #$CTPSModPath = $CTPSModules[0].ModuleBase #$AVSearchList = import-csv -Path "$($CTPSModPath)\antiviruslist.csv" Function ConvertTo-Hex { Param([int]$Number) '0x{0:x}' -f $Number } [system.Version]$OSVersion = (Get-WmiObject win32_operatingsystem -computername $Computername).version #initialize an hashtable of paramters to splat to Get-CimInstance IF ($OSVersion -ge [system.version]'6.0.0.0') { Write-Verbose "OS Windows Vista/Server 2008 or newer detected" $cimParams = @{ Namespace = "root/SecurityCenter2" ClassName = "AntiVirusProduct" # ErrorAction = "Stop" } } Else { Write-Verbose "Windows 2000, 2003, XP detected" $cimParams = @{ Namespace = "root/SecurityCenter" ClassName = "AntiVirusProduct" # ErrorAction = "Stop" } } # end IF ($OSVersion -ge 6.0) #Test for SecurityCenter(2) existance and if not, run as server try { $CIMTest = Get-CimInstance @CimParams -ErrorAction SilentlyContinue -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) if ($CIMTest) { $runAsServer = $False Write-Verbose "$($cimParams.Namespace) found in WMI" } else { $runAsServer = $True Write-Verbose "$($cimParams.Namespace) not found in WMI" } } catch { $runAsServer = $True Write-Verbose "$($cimParams.Namespace) not found in WMI" } If ($All) { Write-Verbose "[BEGIN ] Getting all AV products" } $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] Using parameter set: $($pscmdlet.ParameterSetName)" Write-Verbose "[PROCESS] PSBoundparameters: " Write-Verbose ($PSBoundParameters | Out-String) if ($pscmdlet.ParameterSetName -eq 'computer') { foreach ($computer in $Computername) { Write-Verbose "[PROCESS] Querying $($computer.ToUpper())" #$cimParams.ComputerName = $computer Try { $AV += Get-CimInstance @CimParams -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) | Where-Object {$null -ne $_.displayName} } Catch { Write-Warning "[$($computer.ToUpper())] $($_.Exception.Message)" $cimParams.ComputerName = $null } } #foreach computer } else { foreach ($session in $CimSession) { Write-Verbose "[PROCESS] Using session $($session.computername.toUpper())" $cimParams.CimSession = $session Try { $AV += Get-CimInstance @CimParams -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) | Where-Object {$null -ne $_.displayName} } Catch { Write-Warning "[$($session.computername.ToUpper())] $($_.Exception.Message)" $cimParams.cimsession = $null } } #foreach computer } foreach ($item in $AV) { Write-Verbose "[PROCESS] Found $($item.Displayname)" $hx = ConvertTo-Hex $item.ProductState $mid = $hx.Substring(3, 2) if ($mid -match "00|01") { $Enabled = $False } else { $Enabled = $True } $end = $hx.Substring(5) if ($end -eq "00") { $UpToDate = $True } else { $UpToDate = $False } 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:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) $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-PS-Standard).ModuleBase Write-Verbose "[PROCESS] ModulePath: $($ModulePath)" $vbsexe = Invoke-Expression -Command "CMD.exe /c CSCRIPT '$($ModulePath)\avstatus.vbs' WRITE" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) -ErrorAction Stop Write-Verbose $vbsexe $AV = @() $cimParams = @{ Namespace = "root/SecurityCenter" ClassName = "AntiVirusProduct" # ErrorAction = "Stop" } Write-Verbose "[PROCESS] Using parameter set: $($pscmdlet.ParameterSetName)" Write-Verbose "[PROCESS] PSBoundparameters: " Write-Verbose ($PSBoundParameters | Out-String) if ($pscmdlet.ParameterSetName -eq 'computer') { foreach ($computer in $Computername) { Write-Verbose "[PROCESS] Querying $($computer.ToUpper())" #$cimParams.ComputerName = $computer Try { $AV += Get-CimInstance @CimParams -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) | Where-Object {$null -ne $_.displayName} } Catch { Write-Warning "[$($computer.ToUpper())] $($_.Exception.Message)" $cimParams.ComputerName = $null } } #foreach computer } else { foreach ($session in $CimSession) { Write-Verbose "[PROCESS] Using session $($session.computername.toUpper())" $cimParams.CimSession = $session Try { $AV += Get-CimInstance @CimParams -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) } Catch { Write-Warning "[$($session.computername.ToUpper())] $($_.Exception.Message)" $cimParams.cimsession = $null } } #foreach computer } foreach ($item in $AV) { Write-Verbose "[PROCESS] Found $($item.Displayname)" $hx = ConvertTo-Hex $item.ProductState $mid = $hx.Substring(3, 2) if ($mid -match "00|01") { $Enabled = $False } else { $Enabled = $True } $end = $hx.Substring(5) if ($end -eq "00") { $UpToDate = $True } else { $UpToDate = $False } 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 $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-Host "ERROR: [L$($line)] $($_.Exception.Message). $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } #process End { If ($All) { Write-Verbose "[RETURN] Returning:" Write-Verbose "$($results | Out-String)" return $results } else { #filter for enabled only Write-Verbose "[RETURN] Returning:" Write-Verbose "$(($results).Where( { $_.enabled }) | Out-String)" return ($results).Where( { $_.enabled }) } Write-Verbose "[END ] Ending: $($MyInvocation.Mycommand)" } #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-Host "ERROR: [L$($line)] $($_.Exception.Message). $($_)" -ErrorId $_.Exception.HResult -Category $_.CategoryInfo.Category -CategoryReason $_.CategoryInfo.Reason -ErrorAction Continue $PScmdlet.ThrowTerminatingError($_) } } } # -------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------- |