I have two different objects.
One would be an WMI Event Log and the other a WMI Management object.
How would I extract the dates from these variable and find a time difference?
Edit:
$start = Get-WmiObject Win32_Process -Filter "commandline like '%something%'" -ComputerName $server | %{ $_.ConvertToDateTime($_.CreationDate) }
$end = Get-WMIObject -ComputerName $server -Query "SELECT TimeGenerated FROM Win32_NTLogEvent WHERE LogFile='Application' AND CategoryString = 'Server Startup'" | Select TimeGenerated -First 1
Sample output of the two:
1/20/2016 3:55:48 PM
#{TimeGenerated=20160120210057.000000-000}
It is necessary to use the -Query for the end time, as the event is distinguished by the Category.
I'm currently getting the following error when trying to subtract the two.
Cannot find an overload for "op_Subtraction" and the argument count: "2".
You can use the subtraction operator (-):
$diff = $end.'Eventlog Time: ' - $start.'ABCD Start Time'
Or pass the values as arguments to New-TimeSpan:
$diff = New-TimeSpan -Start $start.'ABCD Start Time' -End $end.'Eventlog Time: '
It seems like you attempt to "optimize" the property names for displaying it later - I would recommend against that, and simply store the DateTime object in the variable by itself:
$start = Get-WmiObject Win32_Process -Filter "commandline like '%something%'" -ComputerName $server | Select -First 1 |%{ $_.ConvertToDateTime($_.CreationDate) }
$end = Get-EventLog System -Newest 1 -ComputerName $server -Source Microsoft-Windows-Winlogon |Select -ExpandProperty TimeGenerated
$diff = $end - $start
Related
The majority of this code was pulled from a blog online, but I think it's exactly the way I need to be tackling this. I want to get the top 4 machines from an OU based on uptime, and run a script that lives on each of the top 4 machines. I know that the problem involves the Array losing access to the Get-ADComputer properties, but I'm unsure of how to pass these new properties back to their original objects. This works as expected until it gets to the foreach loop at the end.
$scriptBlock={
$wmi = Get-WmiObject -Class Win32_OperatingSystem
($wmi.ConvertToDateTime($wmi.LocalDateTime) – $wmi.ConvertToDateTime($wmi.LastBootUpTime)).TotalHours
}
$UpTime = #()
Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree `
| ForEach-Object { $Uptime += `
(New-Object psobject -Property #{
"ComputerName" = $_.DNSHostName
"UpTimeHours" = (Invoke-Command -ComputerName $_.DNSHostName -ScriptBlock $scriptBlock)
}
)
}
$UpTime | Where-Object {$_.UpTimeHours -ne ""} | sort-object -property #{Expression="UpTimeHours";Descending=$true} | `
Select-Object -Property ComputerName,#{Name="UpTimeHours"; Expression = {$_.UpTimeHours.ToString("#.##")}} | Select-Object -First 4 |`
Format-Table -AutoSize -OutVariable $Top4.ToString()
foreach ($Server in $Top4.ComputerName) {
Invoke-Command -ComputerName $Server -ScriptBlock {HOSTNAME.EXE}
}
I'm not married to Invoke-Command in the last foreach but am having the same issues when I try to use psexec. Also, I'm running hostname.exe as a check to make sure I'm looping through the correct machines before I point it at my script.
Here's a streamlined version of your code, which heeds the comments on the question:
# Get all computers of interest.
$computers = Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree
# Get the computers' up-times in hours.
# The result will be [double] instances, but they're also decorated
# with .PSComputerName properties to identify the computer of origin.
$upTimes = Invoke-Command -ComputerName $computers.ConputerName {
((Get-Date) - (Get-CimInstance -Class Win32_OperatingSystem).LastBootUpTime).TotalHours
}
# Get the top 4 computers by up-time.
$top4 = $upTimes | Sort-Object -Descending | Select-Object -First 4
# Invoke a command on all these 4 computers in parallel.
Invoke-Command -ComputerName $top4.PSComputerName -ScriptBlock { HOSTNAME.EXE }
I am trying to use Powershell to pull system event logs for a specific set of event IDs (including shutdown and start events) for the past week while excluding a specific window of time each day which is used for scheduled reboots (and therefore are not going to be relevant shutdown and start events for the data I am trying to collect).
I can filter the events for the past 7 days:
$fileDate = Get-Date -UFormat "%Y-%m-%d"
$logDate = (Get-Date).AddDays(-7)
Foreach ($Computer in $C)
{
$Computer;
Get-WinEvent -Computername $Computer -FilterHashtable #{logname='System'; id=12,13,27,33; StartTime=$logDate} | Format-Table -AutoSize -Wrap | Out-String -Width 4096 > "$env:userprofile\Desktop\$env:computername-$fileDate.txt"
}
This will include all events for those 7 days, including the ones associated with the reboot window each day, which I do not want.
I can filter the events to exclude my specified time window:
$fileDate = Get-Date -UFormat "%Y-%m-%d"
Foreach ($Computer in $C)
{
$Computer;
Get-WinEvent -Computername $Computer -FilterHashtable #{logname='System'; id=12,13,27,33; StartTime="00:00"; EndTime="04:00"} | Format-Table -AutoSize -Wrap | Out-String -Width 4096 > "$env:userprofile\Desktop\$env:computername-$fileDate.txt"
Get-WinEvent -Computername $Computer -FilterHashtable #{logname='System'; id=12,13,27,33; StartTime="05:00"; EndTime="23:59"} | Format-Table -AutoSize -Wrap | Out-String -Width 4096 >> "$env:userprofile\Desktop\$env:computername-$fileDate.txt"
}
This will filter out the hour window I don't want to see events for, but will only capture events for the current day.
I have not figured out a good way to combine these two in order to pull 7 days' worth of logs while excluding the 1 hour window I want ignored. I searched around but couldn't find something indicating exactly what I was looking for. If I missed it in my searches, please point me in the right direction.
How about adding where-object( "?{}" part ) to the result like this?
Get-WinEvent -Computername $Computer -FilterHashtable #{logname='System'; id=12,13,27,33; StartTime=$logDate} |`
?{ (($_.TimeCreated.Hour -ge 00) -AND ($_.TimeCreated.Hour -lt 04)) -OR`
(($_.TimeCreated.Hour -ge 05) -AND ($_.TimeCreated.Hour -le 23))} | `
Format-Table -AutoSize -Wrap |`
Out-String -Width 4096 >> "$env:userprofile\Desktop\$env:computername-$fileDate.txt"
Or if you want to exclude anything that happened during 04:00 - 05:00, maybe this is simpler
Get-WinEvent -Computername $Computer -FilterHashtable #{logname='System'; id=12,13,27,33; StartTime=$logDate} |`
?{ $_.TimeCreated.Hour -ne 04} | `
Format-Table -AutoSize -Wrap |`
Out-String -Width 4096 >> "$env:userprofile\Desktop\$env:computername-$fileDate.txt"
I am a complete noob with regards to programming logic and some help would be greatly appreciated. My question concerns the Get-WmiObject win32_bios and Get-WmiObject win32_computersystem commandlets in the Try Block. The both work as expected if they are in there without the other, but not together. It produces an email report with all computers in domain that looks something like this:
ServerName BIOS version Serial Number
exserver DELL - 1 Phoenix ROM BIOS PLUS Version 1.10 2.7.0 3ZTVDC1
I want to add the model number, but that property is not in win32_bios (at least that I know of). So, I was going to grab it from win32_computersystem. Like I said, them both work, just not together. It always just runs whichever commandlet is first and then goes to the next computer in the list.
Import-Module ActiveDirectory
function getthebios {
$badcomp = #()
$CompList = Get-ADComputer -Filter 'name -like "*"' | select -ExpandProperty Name
foreach ($c in $CompList) {
Try {
Get-WmiObject win32_bios -ComputerName $c |
Select-Object #{l='ServerName';e= {$_.__SERVER} },
#{l='BIOS version';e = {$_.BIOSVersion} },
#{l='Serial Number';e = {$_.SerialNumber} }
Get-WmiObject win32_computersystem -ComputerName $c |
Select-Object #{l='Model Number';e = {$_.Model} }
}
Catch {
$badcomp += $c
}
}
"the following servers could not be reached:"
$badcomp
}
Send-MailMessage -To person#example.com -From "person#example.com" -SmtpServer
server.domain.net -Subject "BIOS Version Report" -body (getthebios | Sort-Object 'BIOS version'
| convertTo-Html | out-string ) -BodyAsHtml
I remember reading about functions returning all output. However the output you are using the second time would not match the object of the previous one. I imagine it is getting discarded due to a mismatch. What you need to do is create a single object with data from both queries. If you have PowerShell 3.0 this should work in place of your Try block
$bios = Get-WmiObject win32_bios -ComputerName $c | Select-Object __SERVER, BIOSVersion, SerialNumber
$computerSystem = Get-WmiObject win32_computersystem -ComputerName $c | Select-Object Model
[psCustomObject]#{
"ServerName" = $bios.__SERVER
"BIOS version" = $bios.BIOSVersion
"Serial Number" = $bios.SerialNumber
"Model" = $computerSystem.Model
}
Capture the output of both WMI calls into variables. Output the results from both in a single object. The Select-Object statements on both lines are not required but since we only need that data it made sense. Now the outputed data will match. The following would work in 2.0 PowerShell if you have that
New-Object psobject -Property #{
"ServerName" = $bios.__SERVER
"BIOS version" = $bios.BIOSVersion
"Serial Number" = $bios.SerialNumber
"Model" = $computerSystem.Model
}
Update from comments
I noticed this as well. What I did to get past that was to cast it as string
"BIOS version" = [string]$bios.BIOSVersion
First time poster here, I'm a bit of a beginner and I've been keen to get my PowerShell scripting skills up to scratch and I'm come across something rather confusing...
I've made a script to query a collection of computers and I want to query Win32_OperatingSystem but only extrapolate the Build number so I can populate my PSObject with it. I'm trying to add some If logic so that if the build number is 7601, I can write a message under my OS column.
The problem I'm having is that the BuildNumber values are coming out as #{BuildNumber=7601} instead of 7601 for instance. That, and my If statement is borked.
$Machines = Get-Content .\Computers.txt
Foreach($Machine in $Machines)
{
$sweet = (Get-WmiObject -Class Win32_OperatingSystem -computer $Machine | Select-Object BuildNumber)
$dversion = if ($sweet -eq "#{BuildNumber=7601}") {Yes!} else {"Nooooo!"}
New-Object PSObject -Property #{
ComputerName = $Machine
Sweet = $sweet
OS = $dversion
}
}
The issue is that the Get-WMIObject cmdlet is returning a Hash Table. Then the Select-Object is returning just the BuildNumber section you want, the BuildNumber property and it's value. You need to add the -ExpandProperty parameter to only get the value back, not the name/value pair.
Get-WMIObject -Class Win32_OperatingSystem | Select-Object BuildNumber
Returns
#{BuildNumber=7601}
With ExpandProperty
Get-WMIObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber
Returns
7601
Just another option with a ping test to skip unavailable machines.
Get-Content .\Computers.txt | Where-Object {Test-Connection -ComputerName $_ -Count 1 -Quiet} | Foreach-Object {
$sweet = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $_ | Select-Object -ExpandProperty BuildNumber
New-Object PSObject -Property #{
ComputerName = $_.__SERVER
Sweet = $sweet
OS = if ($sweet -eq 7601) {'Yes!'} else {'Nooooo!'}
}
}
I am trying to get a list of running processes and filter by two process names - can any one tell me how to get this working?
I've so far got it working and filtering out one process name:
$rn = Get-WMIObject Win32_Process -computer servername `
-credential mydomain\administrator -filter "Name='program1.exe'" |
select -expand path
$lst = Get-Content “C:\path\path2\List.txt”
Compare-Object $lst $rn
What I want it to do is filter two process names but nothing I've tried works. Any ideas?
Here's how to get a complete set of Process objects which match a list of process names you're interested in.
$ProcessNames = #( 'explorer.exe', 'notepad.exe' )
Get-WmiObject Win32_Process -Computer 'localhost' |
Where-Object { $ProcessNames -contains $_.Name } |
Select-Object ProcessID, Name, Path |
Format-Table -AutoSize
This example finds all processes, then filters that list by sending them to a pipeline filter that checks to see if the process name is contained in the list of interesting process names. The main benefit of using the pipeline this way is that you can easily access other attributes (such as ProcessID) of the returned processes.
ProcessID Name Path
--------- ---- ----
5832 explorer.exe C:\Windows\Explorer.EXE
4332 notepad.exe C:\Windows\system32\NOTEPAD.EXE
2732 notepad.exe C:\Windows\system32\notepad.exe
Use WQL operators like OR, AND, LIKE etc:
Get-WMIObject Win32_Process -computer servername -credential mydomain\administrator -filter "Name='program1.exe' OR Name='program2.exe'"
Create an array of the processes you're after:
$processes = #('winword.exe', 'notepad.exe', 'excel.exe') | `
% {
$rn = Get-WMIObject Win32_Process -computer servername -credential mydomain\admin -filter "Name='$_'" | select -expand path
#$lst = Get-Content “C:\path\path2\List.txt”
#Compare-Object $lst $rn
write-host $rn
}
I've commented out your compare so you can see how we are looping through the array clearly.
if I understood well try this:
$rn = Get-WMIObject Win32_Process -computer servername -credential mydomain\administrator -filter "Name='program1.exe OR Name='program2.exe'"
Compare-Object $rn[0].path $rn[1].path # if there are only one instance for process with name program1.exe and program2.exe