$Servers = Get-Content "D:\Server\Servers.txt"
#Gathering Vars
$OSVersion = (Get-CimInstance Win32_OperatingSystem).version
$WarningPreference = 'SilentlyContinue'
$key = 'HKLM:\Software\Company\SCCMMetaData'
$StampCheck = (Get-ItemProperty -Path $key).PatchRelease
$data = foreach ($server in $Servers) {
#Gathering OS Version & Normalization of data
If ($OSVersion -like "10.*") { $OSName = "WINS2016-"; $KB = "KB4540670", "KB4538461", "KB4550929", "KB4556813", "KB4549949", "KB4550994", "KB4494175", "KB4540723" }
Elseif ($OSVersion -like "6.0.*") { $OSName = "WINS08R1-"; $KB = "KB4534303", "KB4534312" }
Elseif ($OSVersion -like "6.1.*") { $OSName = "WINS08R2-"; $KB = "KB4534314", "KB4539602", "KB4534310" }
Elseif ($OSVersion -like "6.2.*") { $OSName = "WINS12R1-"; $KB = "KB4541510", "KB4550917", "KB4556840", "KB4540726" }
Elseif ($OSVersion -like "6.3.*") { $OSName = "WINS12R2-"; $KB = "KB4541509", "KB4550961", "KB4556846", "KB4540725" }
#Check to see if KB is installed & build the stamp
Try {
$KBSearch = Get-HotFix -id $KB -ErrorAction Stop
$Stamp = $OSName
If ($StampCheck -eq "2020Q2") {
Return "$Server Already Compliant"
}
Else {
Set-ItemProperty -path 'HKLM:\Software\Company\SCCMMetaData' -Name 'PatchRelease' -Value $Stamp
Restart-Service -DisplayName 'Agent' -ErrorAction Ignore
Start-Sleep 30
Return "$Server Stamped"
}
}
Catch { Return "Missing Patch $KB on server $Server" }
}
Restart-Service -DisplayName ' Agent'
$data | Export-Csv "D:\Scripts\KB.csv" -NoTypeInformation
This is my code and it is not iterating for all the servers in the .txt file. It is only taking 1st server in the list .
It is not checking for each server in the txt file . Where i am doing the mistake ? Could any one help me ?
return will cause PowerShell to... well, return control to the caller :)
Simply omit the return keyword and leave the expressions following them as-is - they'll automatically "bubble up" to the caller anyway:
#Check to see if KB is installed & build the stamp
Try {
$KBSearch = Get-HotFix -id $KB -ErrorAction Stop
$Stamp = $OSName
If ($StampCheck -eq "2020Q2") {
"$Server Already Compliant"
}
Else {
Set-ItemProperty -path 'HKLM:\Software\Company\SCCMMetaData' -Name 'PatchRelease' -Value $Stamp
Restart-Service -DisplayName 'Agent' -ErrorAction Ignore
Start-Sleep 30
"$Server Stamped"
}
}
Catch { "Missing Patch $KB on server $Server" }
You can read more about the return keyword in the about_Return helpfile
Related
I am seeking help to get output in csv format. I have written powershell code and would want to tweak the output to get in csv format as shown in below pic.
$servers = Get-Content 'C:\Temp\listofservers.txt'
foreach ($server in $servers)
{
#DHCP
if (((Get-Service -ComputerName $server -ServiceName 'DHCPServer' -ErrorAction SilentlyContinue).Status) -eq 'Running')
{
if ((((Get-DhcpServerv4Scope -ComputerName $server | Get-DhcpServerv4Lease -ComputerName $server) | Measure-Object).Count) -ge 1)
{
Write-Host "DHCP present on $server and in use"
}
else
{
Write-Host "DHCP present on $server and not in use"
}
}
else
{
Write-Host("DHCP is not present on $server")
}
#Certificate authority
if (((Get-Service -ComputerName $server -ServiceName 'CertSvc' -ErrorAction SilentlyContinue).Status) -eq 'Running')
{
Write-Host "Certificate Authority is present on $server"
}
else
{
Write-Host "Certificate Authority is not present on $server"
}
}
Send all outputs to a custom object then export them:
$servers = get-content "C:\Temp\listofservers.txt"
$ExportPath = 'c:\temp\results.csv'
$Servers | ForEach-Object {
Write-Host "Checking $_"
# DHCP status
# Use Try....Catch to trap the errors - it's more robust than
# If...Then and prevents a wall of red text if something goes wrong
Try {
$DHCPStatus = Get-Service -ComputerName $_ -Name 'DHCPServer' -ErrorAction Stop
If ($DHCPStatus.Status -eq "Running") {
Try {
If ((((Get-DhcpServerv4Scope -ComputerName $_ -ErrorAction Stop | Get-DhcpServerv4Lease -ComputerName $_ -ErrorAction Stop) | Measure-Object).Count) -ge 1) {
$DHCPResult = "DHCP present on, in use"
}
Else {
$DHCPResult = "DHCP present, not in use"
}
}
Catch {
$DHCPResult = "DHCP present - error obtaining details: $_"
}
}
}
Catch {
$DHCPResult = "DHCP is not present"
}
#Certificate authority
Try {
If (((Get-Service -ComputerName $_ -ServiceName 'CertSvc' -ErrorAction Stop).Status) -eq "Running") {
$CAResult = "Certificate Authority is present"
}
}
Catch {
$CAResult = "Certificate Authority is not present"
}
[pscustomobject]#{ComputerName = $_;DHCP = $DHCPResult;CA=$CAResult}
} | Export-Csv -Path $ExportPath -NoTypeInformation
Use a hashtable to build your object, adding key/values for each property along the way. Then convert the hashtable to a [PSCustomObject] and output it capturing in a variable ($results). Finally, export it to csv using Export-Csv
$servers = Get-Content 'C:\Temp\listofservers.txt'
$results = foreach ($server in $servers) {
# Create hashtable to build object and add ComputerName property
$output = [ordered]#{ComputerName = $server }
#DHCP
if (((Get-Service -ComputerName $server -ServiceName 'DHCPServer' -ErrorAction SilentlyContinue).Status) -eq 'Running') {
if ((((Get-DhcpServerv4Scope -ComputerName $server |
Get-DhcpServerv4Lease -ComputerName $server) |
Measure-Object).Count) -ge 1) {
# add Dhcp property if present, in use
$output.Add('Dhcp', 'Present, in use')
}
else {
# add Dhcp property if present, not in use
$output.Add('Dhcp', 'Present, not in use')
}
}
else {
# add Dhcp property if not present
$output.Add('Dhcp', 'Not present')
}
#Certificate authority
if (((Get-Service -ComputerName $server -ServiceName 'CertSvc' -ErrorAction SilentlyContinue).Status) -eq 'Running') {
# add CA property if present
$output.Add('CA', 'Present')
}
else {
# add CA property if not present
$output.Add('CA', 'Not present')
}
# Convert hashtable to pscustomobject and output it
[PSCustomObject]$output
}
# output results to screen
$results
# export results to csv file
$results | Export-Csv -Path c:\temp\server_dhcp_ca_check.csv -NoTypeInformation
I'm using the following code to give me output on the status of a batch of computers:
$Win2k8r2Computers = "Server1", "Server2", "Server3", "Server4"
$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
$props = #{}
Try {
<#If ($PSVersionTable.PSVersion.Major -eq "2") {
$props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
}#>
If (Get-Service | Where-Object { $_.Name -eq "W3SVC" } -ErrorAction Stop) {
$props.Add('Message', "IIS is installed (Win2008r2)")
}
Else {
$props.Add('Message', "IIS is NOT installed (Win2008r2)")
}
}
catch {
$props.Add('Message', 'Error: {0}' -f $_)
}
New-Object -Type PSObject -Prop $Props
}
It's working as expected other than the catch not appearing to actually catch and return errors to the $results variable. What am I missing?
In your current code, you are passing parameter -ErrorAction only to Where-Object. So you would only catch errors of the Where-Object cmdlet. Get-Service woud still run with the default ErrorAction value of Continue.
To turn errors of both Get-Service and Where-Object into terminating errors that can be catched, either pass -ErrorAction 'Stop' to both of them...
If (Get-Service -ErrorAction Stop | Where-Object { $_.Name -eq "W3SVC" } -ErrorAction Stop)
... or (more efficiently) set the $ErrorActionPreference variable at the beginning of the script and remove the -ErrorAction parameter:
$Win2k8r2Computers = "Server1", "Server2", "Server3", "Server4"
$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
$props = #{}
Try {
$ErrorActionPreference = 'Stop'
<#If ($PSVersionTable.PSVersion.Major -eq "2") {
$props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
}#>
If (Get-Service | Where-Object { $_.Name -eq "W3SVC" }) {
$props.Add('Message', "IIS is installed (Win2008r2)")
}
Else {
$props.Add('Message', "IIS is NOT installed (Win2008r2)")
}
}
catch {
$props.Add('Message', 'Error: {0}' -f $_)
}
New-Object -Type PSObject -Prop $Props
}
Caveat:
$ErrorActionPreference is ignored by cmdlets in PowerShell script modules, unless they take special care of handling it.
See this answer for some insight.
In this case it works though, because both cmdlets are implemented in C# modules.
Looking for advice on error handling in Powershell. I think I understand the concept behind using Try/Catch but I'm struggling on where to utilize this in my scripts or how granular I need to be.
For example, should I use the try/catch inside my functions and if so, should I insert the actions of my function inside the try or do I need to break it
down further? OR, should I try to handle the error when I call my function? Doing something like this:
Try{
Get-MyFunction
} catch{ Do Something"
}
Here's an example of a script I wrote which is checking for some indicators of compromise on a device. I have an application that will launch this script and capture the final output. The application requires the final output to be in the following format so any failure should generate this.
[output]
result=<0 or 1>
msg= <string>
Which I'm doing like this:
Write-Host "[output]"
Write-Host "result=0"
Write-Host "msg = $VariableContainingOutput -NoNewline
Two of my functions create custom objects and then combine these for the final output so I'd like to capture any errors in this same format. If one function generates an error, it should record these and continue.
If I just run the code by itself (not using function) this works but with the function my errors are not captured.
This needs to work on PowerShell 2 and up. The Add-RegMember and Get-RegValue functions called by this script are not shown.
function Get-ChangedRunKey {
[CmdletBinding()]
param()
process
{
$days = '-365'
$Run = #()
$AutoRunOutput = #()
$RunKeyValues = #("HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run"
)
Try{
$Run += $RunKeyValues |
ForEach-Object {
Get-Item $_ -ErrorAction SilentlyContinue |
Add-RegKeyMember -ErrorAction SilentlyContinue |
Where-Object {
$_.lastwritetime -gt (Get-Date).AddDays($days)
} |
Select-Object Name,LastWriteTime,property
}
if ($Run -ne $Null)
{
$AutoRunPath = ( $Run |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$AutoRunValue = $AutoRunPath |
Where-Object {
$_ -and $_.Trim()
} |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
}
}
#Build Custom Object if modified Run keys are found
if($AutorunValue -ne $null)
{
foreach ($Value in $AutoRunValue) {
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = $Value.path
value = $Value.value
}
}
}
Write-Output $AutoRunOutput
}catch{
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = "N/A"
value = "Error accessing Autorun data. $($Error[0])"
}
}
}
}
function Get-ShellIOC {
[CmdletBinding()]
param()
process
{
$ShellIOCOutput = #()
$ShellIOCPath = 'HKU:\' + '*' + '_Classes\*\shell\open\command'
Try{
$ShellIOCValue = (Get-Item $ShellIOCPath -ErrorAction SilentlyContinue |
Select-Object name,property |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$ShellIOCDetected = $ShellIOCValue |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
} |
Where-Object {
$_.value -like "*cmd.exe*" -or
$_.value -like "*mshta.exe*"
}
if($ShellIOCDetected -ne $null)
{
foreach ($ShellIOC in $ShellIOCDetected) {
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = $ShellIOC.path
value = $ShellIOC.value
}
}
}
Write-Output $ShellIOCOutput
}catch{
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = "N/A"
value = "Error accessing ShellIOC data. $($Error[0])"
}
}
}
}
function Set-OutputFormat {
[CmdletBinding()]
param()
process
{
$FormattedOutput = $AutoRunOutput + $ShellIOCOutput |
ForEach-Object {
"Description:" + $_.description + ',' + "Path:" + $_.path + ',' + "Value:" + $_.value + "|"
}
Write-Output $FormattedOutput
}
}
if (!(Test-Path "HKU:\")){
try{
New-PSDrive -PSProvider Registry -Root HKEY_USERS -Name HKU -ErrorAction Stop | Out-Null
}catch{
Write-Output "[output]"
Write-Output "result=0"
Write-Host "msg = Unable to Connect HKU drive" -NoNewline
}
}
$AutoRunOutput = Get-ChangedRunKey
$ShellIOCOutput = Get-ShellIOC
$FormattedOutput = Set-OutputFormat
Write-Output "[output]"
if ($FormattedOutput -eq $Null)
{
Write-Output "result=0"
Write-Host "msg= No Items Detected" -NoNewline
}
else
{
Write-Output "result=1"
Write-Host "msg=Items Detected: $($FormattedOutput)" -NoNewline
}
You have to know that there are 2 error types in PowerShell:
Terminating Errors: Those get caught automatically in the catch block
Non-Terminating Error: If you want to catch them then the command in question needs to be execution using -ErrorAction Stop. If it is not a PowerShell command but an executable, then you need to check stuff like the exit code or $?. Therefore I suggest wrapping your entire action in an advanced function on which you then call using -ErrorAction Stop.
Apart from that I would like to remark that PowerShell version 2 has already been deprecated. The reason for why non-terminating errors exists is because there are cases like for example processing multiple objects from the pipeline where you might not want it to stop just because it did not work for one object. And please do not use Write-Host, use Write-Verbose or Write-Output depending on the use case.
So I've been using Split-Pipeline module for some time now, and what bothers me about it, rarely does it really finish. I always get stuck with the last job in the queue hanging and have to either stop the script or kill ISE:
VERBOSE: Split-Pipeline: Jobs = 1; Load = End; Queue = 0
example: 300 servers, running a scan for hotfixes on them using split-pipeline. I'm using pretty standard parameters:
$servers | Split-Pipeline -Verbose -Count 10 { process {#some code for scanning#}}
So 10 jobs each loaded with 30 servers, first lets say 250 servers are scanned really fast, then it slows down a little and when the last job remains only, it never finishes...
Anyone experienced something similar? I've tested the same on several machines and it's always the same so I don't think it's related to the machine running the script.
EDIT: heres the code
$servers = (Get-Clipboard).trim() #server1..100
$KB = (Get-Clipboard).trim() -split ', ' #KB4022714, KB4022717, KB4022718, KB4022722, KB4022727, KB4022883, KB4022884, KB4022887, KB4024402, KB4025339, KB4025342, KB4021903, KB4021923, KB4022008, KB4022010, KB4022013, KB3217845
$servers | Split-Pipeline -Verbose -Variable KB -Count 10 { process {
$hash = [ordered]#{}
try
{
$hash.Hostname = $_
$os = gwmi win32_operatingsystem -ComputerName $_ -ErrorAction Stop
$hash.OS = $os.Caption
$hash.Architecture = $os.OSArchitecture
$today = [Management.ManagementDateTimeConverter]::ToDateTime($os.LocalDateTime)
$hash.LastReboot = [Management.ManagementDateTimeConverter]::ToDateTime($os.LastBootUpTime)
$hash.DaysSinceReboot = ($today - $hash.LastReboot).Days
}
catch
{
$hash.OS = $Error[0]
$hash.Architecture = 'N/A'
$hash.LastReboot = 'N/A'
$hash.DaysSinceReboot = 'N/A'
}
try
{
$hash.PendingReboot = icm -cn $_ {
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA SilentlyContinue) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA SilentlyContinue) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -Ea SilentlyContinue) { return $true }
try
{
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending)
{
return $true
}
}
catch{}
return $false
}
}
catch
{
$hash.PendingReboot = 'N/A'
}
try
{
$hotfix = Get-HotFix -ComputerName $_ -Id $KB -ErrorAction Stop
if ($hotfix)
{
$hash.Hotfix = $hotfix.HotFixID -join ','
}
else
{
$hash.Hotfix = "No Hotfix from the list applied"
}
}
catch
{
$hash.Hotfix = $Error[0]
}
$obj = New-Object -TypeName PSObject -Property $hash
$obj | Export-Csv c:\temp\hotfixes.csv -NoTypeInformation -Append -Force
}
}
So I made this script to find the next avaible script in a series. The computer will get a name with ComputerXXXX (XXXX being 0-9999). It works when I run it manually after the TS is done, but not when I run it through TS as a step. Any tips? It does print the information into the .csv file so the script does run. Have been reading about OSDComputername or something, any info on this?
# Variables debugging
$global:navneserie = $null
$global:navneserieNavn = $null
$computerName = $env:COMPUTERNAME
$Dato = (get-date)
$log = "Enabled"
write-host $computername
import-module ActiveDirectory
write-host $serialnumber
$global:navneserie = "Computer"
$global:navneserieNavn = "Computer"
function SCCMLog
{
if ($log -eq "Enabled")
{
add-content \\DOMAINNAME\NETLOGON\SCCMLog\SCCMlog.csv "$Dato;$Computername; ;OK"
}
}
if ($env:computerName -like "Computer*")
{
write-host $computername
exit
}
else
{
$OpptatteNavn = Get-ADComputer -properties name -filter *| where {$_.name -match $global:navneserie} | ForEach-Object {$_.Name} | sort
[int]$forrige = 9999
[int]$denne = 0000
$funn = $false
While (-not $funn)
{
Foreach ($navn in $OpptatteNavn)
{
$navn = $navn.substring($navn.length -4,4)
$denne = $navn
If ((($denne-$forrige) -gt 1)-and -not $funn)
{
[int]$nesteLedige = $forrige + 1
$funn = $true
break
}
$forrige = $navn
}
}
If ($nesteLedige -lt 10)
{
$000Name = $global:navneserieNavn+"000"+$nesteLedige
rename-computer -newname "$000Name"
write-host "Datamaskinen har blitt renamet til:"$global:navneserieNavn"000"$nesteLedige"`n"
LogSCCM
}
elseIf ($nesteLedige -lt 100)
{
$00Name = $global:navneserieNavn+"00"+$nesteLedige
rename-computer -newname "$00Name"
write-host "Datamaskinen har blitt renamet til:"$global:navneserieNavn"00"$nesteLedige"`n"
LogSCCM
}
elseIf ($nesteLedige -lt 1000)
{
$0Name = $global:navneserieNavn+"0"+$nesteLedige
rename-computer -newname "$0Name"
write-host "Datamaskinen har blitt renamet til:"$global:navneserieNavn"0"$nesteLedige
LogSCCM
}
else
{
rename-computer = $global:navneserieNavN$nesteLedige
write-host "Datamaskinen har blitt renamet til:$global:navneserieNavn$nesteLedige"
LogSCCM
}