Empty variable after Invoke-Command - powershell

I want to get the most errors of a list of servers and save it in the variable $AllErrors.
But the variable is empty if I want to print it out.
Is there a way to pass the variable out of the Invoke-Command?
This is my code:
Get-Content -Path C:\Users\$env:USERNAME\Desktop\Server.txt |
ForEach-Object{
Invoke-Command -ComputerName $_ -ScriptBlock{
$Date = (Get-Date).AddHours(-12)
$Events = Get-WinEvent -FilterHashtable #{LogName = 'System'; StartTime = $Date; Level = 2}
$Errors = $Events | Group-Object -Property ID -NoElement | Sort-Object -Property Count -Descending |
Select-Object -Property Name -First 1
}
}
$Errors | Out-File -FilePath C:\Users\$env:USERNAME\Desktop\AllErrors.txt

No, it's not possible, however you can assign the output of Invoke-Command to a variable. $Errors seems to be the only output of your Invoke-Command, so this should work. But looking at your code you will only get Errors for the last computer as you are constantly overwriting the $Errors variable in cycle. You should either declare $Errors outside of cycle, and append errors to it, or append to file after each cycle:
Get-Content -Path C:\Users\$env:USERNAME\Desktop\Server.txt |
ForEach-Object{
$Errors = Invoke-Command -ComputerName $_ -ScriptBlock{
$Date = (Get-Date).AddHours(-12)
$Events = Get-WinEvent -FilterHashtable #{LogName = 'System'; StartTime = $Date; Level = 2}
$Events | Group-Object -Property ID -NoElement | Sort-Object -Property Count -Descending |
Select-Object -Property Name -First 1
}
$Errors | Out-File -FilePath C:\Users\$env:USERNAME\Desktop\AllErrors.txt -Append
}

Related

How to check Veeam Backup Jobs Status in ALL servers

I have script that checks every 24 hours locally on server the status of all backup jobs along more details.
I want that script to check all my servers, lets say: "SRV1", "SRV2", "SRV3"
How can i manage that?
Here's the script:
$date = (Get-Date).AddHours(-24)
$sessions = Get-VBRComputerBackupJobSession
foreach ($PBackup_job in (Get-VBRComputerBackupJob | Select Name)) {
$PBackup_job_name = $PBackup_job.Name
write "------------ Physical Server Backup Job Name : $PBackup_job_name ------------"
$sessions | where {$_.CreationTime -ge $date} | sort CreationTime | Select CreationTime, endtime, result, state | Format-Table
}
Although I cannot test this myself, I think you could do that using Invoke-Command like below:
$servers = "SRV1", "SRV2", "SRV3"
# set the credentials for admin access on the servers
$cred = Get-Credential 'Please enter your admin credentials'
$result = Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
$date = (Get-Date).AddHours(-24).Date
$sessions = Get-VBRComputerBackupJobSession
foreach ($PBackup_job in (Get-VBRComputerBackupJob)) {
$sessions | Where-Object {$_.CreationTime -ge $date} |
Sort-Object CreationTime |
Select-Object #{Name = 'Server'; Expression = {$env:COMPUTERNAME}},
#{Name = 'BackupJobName'; Expression = {$PBackup_job.Name}},
CreationTime, endtime, result, state
}
}
# remove the extra properties PowerShell added and save to CSV
$result = $result | Select-Object * -ExcludeProperty PS*, RunSpaceId
# output on screen
$result | Format-Table -AutoSize
# write to file
$result | Export-Csv -Path 'X:\Somewhere\BackupJobs.csv' -NoTypeInformation

Duration of specific task in task scheduler

Taking inspiration on Get informations on Windows Scheduled Task duration (execution time)
we aim to filter on a set of tasks, instead of fetching all from task scheduler.
something of this sort:
$logName = 'Microsoft-Windows-TaskScheduler/Operational'
$xPathFilter = #'
*[
System[(EventID=200 or EventID=201)] and
EventData[
Data[#Name="TaskName"] = "001_task_A_nightly"
]
]
'#
Get-WinEvent -LogName $logName -FilterXPath $xPathFilter | Group-Object ActivityID | ForEach-Object {
$start = $_.Group |
Where-Object { $_.Id -eq 200 } |
Select-Object -Expand TimeCreated -First 1
$end = $_.Group |
Where-Object { $_.Id -eq 201 } |
Select-Object -Expand TimeCreated -First 1
New-Object -Type PSObject -Property #{
'TaskName' = $_.Group[0].Properties[0].Value
'Duration' = ($end - $start).TotalSeconds
Is there a way to adapt this one above?
Use the -FilterXPath parameter set instead, it'll allow you to granularly filter on the contents of the underlying event XML:
$logName = 'Microsoft-Windows-TaskScheduler/Operational'
$xPathFilter = #'
*[
System[(EventID=200 or EventID=201)] and
EventData[
Data[#Name="TaskName"] = "\MyTask" or
Data[#Name="TaskName"] = "\MyOtherTask" or
Data[#Name="TaskName"] = "\TaskFolder\SomeThirdTask"
]
]
'#
Get-WinEvent -LogName $logName -FilterXPath $xPathFilter |Group-Object ...

having issues recording error in export-csv

I have powershell script to pull down hotfixID, installedon, lastbootuptime and freespace in C drive. (I googled around and changed couple of things I need.) when the Pc is not reachable it will
Write-Warning "$_ cannot be reached, skipping."
I also want to capture the computer name of the failed PC to my CSV. I tried
| Export-Csv C:\test\computerDetails.csv -NoTypeInformation
or append but seems like its not working. can someone please help? below is my whole script.
(Get-Content C:\test\serverlist.txt).Trim() | ForEach {
If (Test-Connection -ComputerName $_ -Count 1 -Quiet)
{
$update = Get-CimInstance Win32_QuickFixEngineering -ComputerName $_ | Sort-Object InstalledOn -Descending | Select-Object -First 1
$os = Get-CimInstance win32_operatingsystem -ComputerName $_
$disk = Get-WmiObject Win32_LogicalDisk -ComputerName $_ -Filter "DeviceID='C:'"
$props = #{
ComputerName = $_
HotFixID = $update.HotFixID
InstalledOn = $update.InstalledOn
lastbootuptime = $os.LastBootUpTime
FreeSpace_GB = $disk.FreeSpace / 1GB
}
New-Object PsObject -Property $props
}
Else {
Write-Warning "$_ cannot be reached, skipping." | Export-Csv C:\test\computerDetails.csv -NoTypeInformation
}
} | Sort ComputerName |
Select ComputerName,HotFixID,InstalledOn,lastbootuptime,FreeSpace_GB |
Export-Csv C:\test\computerDetails.csv -NoTypeInformation
Main problem with adding it to the CSV is that it is a string. If you treat the erroneous machines the same as successful ones, then you can throw them in the same CSV.
I have added an ArrayList there as the storage variable and then for each computer it creates a temp PSObject to store your results in, overwriting the variable each loop but not before dumping the variable into the ArrayList for export at the end.
$Errors = New-Object System.Collections.ArrayList
(Get-Content C:\test\serverlist.txt).Trim() | ForEach {
$Temp = New-Object -TypeName PSObject
If (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
$update = Get-CimInstance Win32_QuickFixEngineering -ComputerName $_ | Sort-Object InstalledOn -Descending | Select-Object -First 1
$os = Get-CimInstance win32_operatingsystem -ComputerName $_
$disk = Get-WmiObject Win32_LogicalDisk -ComputerName $_ -Filter "DeviceID='C:'"
$props = [ordered]#{
ComputerName = $_
HotFixID = $update.HotFixID
InstalledOn = $update.InstalledOn
lastbootuptime = $os.LastBootUpTime
FreeSpace_GB = $disk.FreeSpace / 1GB
Error = "Success"
}
$Temp | Add-Member -NotePropertyMembers $props -TypeName temp
} Else {
$props = [ordered]#{
ComputerName = $_
Error = "Cannot be reached"
}
$Temp | Add-Member -NotePropertyMembers $props -TypeName temp
Write-Warning "$_ cannot be reached, skipping."
}
$Errors.Add($Temp) > $null
}
$Errors | Export-Csv C:\temp\computerDetails.csv -NoTypeInformation -Append

Query list of computers - output last logged on user and last logon date

I am creating a script to retrieve all the machine names from a .txt file then Query against them;
ComputerName
UserName (Of the last person to logon to the machine)
Date it was last Logged on to/Used
This is what i have
Clear-Host
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
ForEach ($Compu in $machines) {
Get-WmiObject –ComputerName $machines –Class Win32_ComputerSystem | Select
Username, PSComputerName | FT
}
As sidenotes:
the hyphens for the parameter names are not hyphens, but En-Dashes, so I gather this code is copied from the internet somewhere
inside the loop you are using the wrong variable on the ComputerName parameter which should be $Compu
Having said that, I don't think you can get the info you need from the WMI Win32_ComputerSystem class..
What you will need to do is to parse the info from the computers eventlog:
# get an array of computernames loaded from the text file
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
$result = foreach ($computer in $machines) {
# test if the compurer is on-line
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer '$computer' is off-line."
# skip this computer and carry on with the next iteration
continue
}
# search the computers eventlog and parse the username and last logon time from that
# you can play around with other values for -MaxEvents if you feel you're missing information.
Get-WinEvent -ComputerName $computer -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 20 |
Where-Object { $_.Properties[1].Value -notmatch 'SYSTEM|NETWORK SERVICE|LOCAL SERVICE' } |
Select-Object #{Name ='ComputerName'; Expression = {$_.MachineName}},
#{Name ='UserName'; Expression = {$_.Properties[1].Value}},
#{Name ='LastLogon'; Expression = {$_.TimeCreated}} -First 1
}
# show on screen:
$result | Format-Table -AutoSize
# save as CSV file
$result | Export-Csv -Path 'D:\LastLogonInfo.csv' -NoTypeInformation
Update
If I understand your comment correctly, you would like a list of all users (except for a few) and retrieve their latest login on a computer from the list.
In that case you can do the following:
# get an array of computernames loaded from the text file
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
$result = foreach ($computer in $machines) {
# test if the compurer is on-line
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer '$computer' is off-line."
# skip this computer and carry on with the next iteration
continue
}
# you do not want to include these account logins
$exclude = '\$|SYSTEM|NETWORK SERVICE|LOCAL SERVICE|KHALIFAM'
# search the computers eventlog and parse the username and last logon time from that
# you can play around with other values for -MaxEvents if you feel you're missing information.
Get-WinEvent -ComputerName $computer -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 100 |
Where-Object { $_.Properties[1].Value -notmatch $exclude } |
Select-Object #{Name ='ComputerName'; Expression = {$_.MachineName}},
#{Name ='UserName'; Expression = {$_.Properties[1].Value}},
#{Name ='LastLogon'; Expression = {$_.TimeCreated}} |
Group-Object -Property UserName | ForEach-Object {
$_.Group | Sort-Object LastLogon -Descending | Select-Object -First 1
}
}
# show on screen:
$result | Format-Table -AutoSize
# save as CSV file
$result | Export-Csv -Path 'D:\LastLogonInfo.csv' -NoTypeInformation

How to fix Get-ChildItem -Recurse to handle more than the first level

I try to filter Windows events (id=4633) from eventlogs out of a given filestructure recursivly with Get-ChildItem.
The filestructure looks like this:
C:\Temp\raw_data\2018-09\Securitylog\Securitylog_2018-09-14_13-30.evtx
The problem is, that the Get-ChildItem only handles the first level. Every level below the first one seems to be ignored.
I tried using the -Recurse parameter without success. I don't receive any errors, so I believe the syntax is correct. The codesample is given below.
$out = New-Object System.Text.StringBuilder
$out.AppendLine("ServerName,EventID,TimeCreated,UserName,File_or_Folder,AccessMask")
$ns = #{e = "http://schemas.microsoft.com/win/2004/08/events/event"}
Get-ChildItem "C:\temp\raw_data" -Recurse | ForEach-Object {
{
$evts = Get-WinEvent -FilterHashtable #{Path=$_;id="4663"} -Oldest
foreach ($evt in $evts) {
$xml = $evt.ToXml()
$SubjectUserName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='SubjectUserName']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$ObjectName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='ObjectName']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$AccessMask = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[#Name='AccessMask']/text()" |
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty Value
$out.AppendLine("$($svr),$($evt.id),$($evt.TimeCreated),$SubjectUserName,$ObjectName,$AccessMask")
Write-Host $svr
Write-Host $evt.id, $evt.TimeCreated, $SubjectUserName, $ObjectName,$AccessMask
}
}
}
$out.ToString() | Out-File -FilePath "C:\Temp\X4663Events.csv"
I have no idea, why no file is processed. The file will be created, but it is empty execpt for the headline.
It doesn't have to be that hard. Typical powershell looks more like this. Build an object, then export to csv. Know what you're doing, and verify each piece.
$evts = get-winevent security -MaxEvents 3
$objs = foreach ($evt in $evts) {
[xml]$xml = $evt.toxml()
$subjectusername = $xml.event.EventData.data |
where name -eq subjectusername |
select -expand '#text'
$subjectdomainname = $xml.event.EventData.data |
where name -eq subjectdomainname |
select -expand '#text'
[pscustomobject]#{
SubjectUserName = $subjectusername
SubjectDomainName = $subjectdomainname
}
}
$objs
$objs | export-csv mylog.csv
get-content mylog.csv
SubjectUserName SubjectDomainName
--------------- -----------------
SYSTEM NT AUTHORITY
MYCOMP$ AD
MYCOMP$ AD
#TYPE System.Management.Automation.PSCustomObject
"SubjectUserName","SubjectDomainName"
"SYSTEM","NT AUTHORITY"
"MYCOMP$","AD"
"MYCOMP$","AD"