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.
Related
I am trying to remove vulnerable classes from the log4j jar file with powershell.
I am able to remove the file using the script locally on the server, however, I want to remove the class from many paths on many servers and trying to use invoke-command.
The script can open and read the JAR file but doesnt seem to action the delete() method. Is there a way to get powershell to delete the class "remotely"?
Here is my script:
$servers = #(
"server"
)
$class_to_delete = "JMSSink"
$unable_to_connect = #()
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
write-host "`nTesting connection to:" -ForegroundColor Yellow
$servers | ForEach-Object {Write-Host "$_"}
$servers | ForEach-Object {
$server = $_
try {
write-host "`nTesting $($server)"
Invoke-Command -ComputerName $server -ScriptBlock {
Write-Host "Connection successful to $($env:computername)" -ForegroundColor Green
} -ErrorAction Stop
} catch {
write-host "`nConnection failed to $($server)"
$unable_to_connect += $server
}
}
Write-Host "`nStarting script to remove $($class_to_delete) class from log4j" -ForegroundColor Yellow
$objects_skipped = #()
$servers | ForEach-Object {
$server_node = $_
write-host "`nPut in the file paths for $($server_node)" -ForegroundColor Yellow
$file_locations = (#(While($l=(Read-Host).Trim()){$l}) -join("`n"))
$file_locations | Out-File C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$file_objects = Get-Content -Path C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$stats_x = foreach ($file_object in $file_objects) {
$stats = Invoke-Command -ComputerName $server_node -ScriptBlock {
Write-Host "`nStarting on $($env:COMPUTERNAME)"
$class = $using:class_to_delete
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
$ful_path = $using:file_object
$fn = Resolve-Path $ful_path
try {
$zip = [System.io.Compression.ZipFile]::Open("$fn", "Read")
Write-Host "Backing up $($fn) to $($fn).bak"
$zip.Dispose()
Copy-Item "$fn" "$($fn).bak"
$zip = [System.io.Compression.ZipFile]::Open($fn, "Update")
$files = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
if (!$files) {
write-host "`nNo $($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
$files.dispose()
$not_found = #()
$not_found += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = "$($class) class NOT FOUND"
}
Write-Output $not_found
} else {
foreach ($file in $files) {
write-host "`n$($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
write-host "`nDeleting file $($file)" -ForegroundColor Green
#delete class
$file.delete()
#check if class was successfully deleted
$confirm_delete = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
write-host $confirm_delete
if ($confirm_delete.name -match "$class.class") {
$deleted_status = "$($class) !!NOT REMOVED!!"
} else {
$deleted_status = "$($class) REMOVED"
}
$Output = #()
$Output += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = $deleted_status
}
Write-Output $Output
}
}
} catch {
Write-Host "Cannot open $($ful_path) as a Zip File. $($Error[0])"
}
}
Write-Output $stats
}
$objects_skipped += $stats_x
}
#result
write-host "`nEnd result"
$objects_skipped | select Server,Result,Path | ft -AutoSize
You need to explicitly call Dispose() on the containing archive to persist the updates to the file on disk:
# this goes immediately after the `catch` block:
finally {
if($zip -is [IDisposable]){ $zip.Dispose() }
}
By placing the call to $zip.Dispose() inside a finally block, we ensure that it's always disposed regardless of whether an exception was thrown in the preceding try block.
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.
Can someone point me in the right direction? Basically, I would like to export the results of my testpath to a csv. Below is what I am working with. I have read a couple Microsoft documents but they only seem to confuse me even more. Any feedback is appreciated.
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
Foreach ($Computer in $ComputerList)
{
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference= 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
}
$Output =New-Object -TypeName PSObject -Property #{
} | Select-Object
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Assuming you want a record per user per computer, there's two things you want to change structurally:
Create new objects in the inner foreach loop
Assign all the objects created to $Output:
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
$Output = Foreach ($Computer in $ComputerList) {
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference = 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
New-Object -TypeName PSObject -Property #{
# We still need a bit of magic here
}
}
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Now we just need to decide on what properties to add to our output objects:
New-Object -TypeName PSObject -Property #{
# We definitely want to know which computer and user profile the results are for!
ComputerName = $Computer
ProfileName = $user.Name
# And finally we want the results of `Test-Path`
Result = $t
}
Here's another option. Though nowhere near as elegant as what Matthias gave you. ;-}
It's just a refactor, to narrow down your code and pass everything directly and output by default, without the need for all the, Write-* stuff and the like. PowerShell just grants a number of ways to accomplish a use case.
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results on screen
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
104DB2FE-76B8-4 no
Localhost no
127.0.0.1 no
#>
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\MicrosoftEdge") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
ComputerName Status
------------ ------
104DB2FE-76B8-4 Has it
Localhost Has it
127.0.0.1 Has it
#>
I am trying to capture the changing variable '$server' everytime the parameters go through a foreach loop. To summarize, the $sever value is always changing, and I want to capture it and add it into a collective csv file
Thank you!
Here is the code main part of the code that I have.
function Convert-QueryToObjects
{
[CmdletBinding()]
[Alias('QueryToObject')]
[OutputType([PSCustomObject])]
param
(
[Parameter(Mandatory = $false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[Alias('ComputerName', 'Computer')]
[string]
$Name = $env:COMPUTERNAME
)
Process
{
Write-Verbose "Running query.exe against $Name."
$Users = query user /server:$Name 2>&1
if ($Users -like "*No User exists*")
{
# Handle no user's found returned from query.
# Returned: 'No User exists for *'
Write-Error "There were no users found on $Name : $Users"
Write-Verbose "There were no users found on $Name."
}
elseif ($Users -like "*Error*")
{
# Handle errored returned by query.
# Returned: 'Error ...<message>...'
Write-Error "There was an error running query against $Name : $Users"
Write-Verbose "There was an error running query against $Name."
}
elseif ($Users -eq $null -and $ErrorActionPreference -eq 'SilentlyContinue')
{
# Handdle null output called by -ErrorAction.
Write-Verbose "Error action has supressed output from query.exe. Results were null."
}
else
{
Write-Verbose "Users found on $Name. Converting output from text."
# Conversion logic. Handles the fact that the sessionname column may be populated or not.
$Users = $Users | ForEach-Object {
(($_.trim() -replace ">" -replace "(?m)^([A-Za-z0-9]{3,})\s+(\d{1,2}\s+\w+)", '$1 none $2' -replace "\s{2,}", "," -replace "none", $null))
} | ConvertFrom-Csv
Write-Verbose "Generating output for $($Users.Count) users connected to $Name."
# Output objects.
foreach ($User in $Users)
{
Write-Verbose $User
if ($VerbosePreference -eq 'Continue')
{
# Add '| Out-Host' if -Verbose is tripped.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
} | Out-Host
}
else
{
# Standard output.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
}
}
}
}
}
}
$Servers = Get-Content 'H:\demo\computernames.txt'
foreach ($Server in $Servers)
{
if (-not( Test-Connection $Server -Count 1 -Quiet )) { continue }
if (-not( Convert-QueryToObjects $Server -ErrorAction SilentlyContinue)) {
$server | Out-File 'H:\demo\session\run1.csv' -Append
}
else
{
Convert-QueryToObjects -Name $Server | select ComputerName, Username, Sessionstate, IdleTime, ID | Export-Csv 'H:\demo\session\run.csv' -NoTypeInformation
}
}
Create an array outside of your foreach loop and add the $server variable value to the array during your foreach. At the end export the array to a csv.
Not tested, but are you wanting to do something like this?
Get-Content "H:\demo\computernames.txt" | ForEach-Object {
$computerName = $_
if ( Test-Connection $computerName -Count 1 -Quiet ) {
Convert-QueryToObjects $computerName -ErrorAction SilentlyContinue
}
else {
"$_ not pingable" | Out-File "H:\demo\session\notpingable.log" -Append
}
} | Export-Csv "H:\demo\session\run.csv" -NoTypeInformation