Powershell - check for last time computers were restarted - powershell

I need to make sure all computers are being restarted at least once a week, and I've been googling on it, and I found something, but it's... rather something simple, something... 1-timer... What I want is to make it a bit more advanced.
Here's the command I found online:
Get-WmiObject Win32_OperatingSystem -ComputerName <computername> : select cname, #(LABEL='LastBootUpTime' ;EXPRESSION=($_.ConverttoDateTime($_.lastbootuptime)))
(I'm not sure if sharing the links is a good idea here, but I just googled for "check when remote computer was last restarted" and clicked on a link on enterprisedaddy)
I do not know anything about PowerShell, so I'm asking for your help. Also, it may be useful not only to me, but to others as well...
Here's what I want:
Create the *.ps1 file (I can do that) and make it run it 24/7
Instead of copying all computers each time, I want to append new computers to the list, because otherwise, the list would be very, very long... and I don't want to be deleting a whole bunch of computers and making sure only 1 copy is left and deleting all other every day...
Export the list into either *.txt or (even better) *.csv
At the end of the day, the *.ps1 would create a new *.txt or *.csv file and start it all over. In a new file. Of course leaving the old one (or all others, starting day 2...) for the review...
I understand there's very little hope, but if you can help me - great. If not - well, it may take forever to google for it all, but in the end, I may be able to find it all myself... Although there is even less hope here...
To be fair, all I need is to see the last time the computers were restarted, and that's all, so if you know of an easier way - I'm ready to read about other ideas.

This is not a full answer, but should help you toward your goal. If I'm interpreting your question correctly you want to check the last boot up time daily and report machines that haven't rebooted in the last 7 days. That data should then be stored in a csv file.
So this is actually quite easy to do, and PowerShell is a great place to do it. However, you have to "develop" on top of what you've already discovered. Set goals and meet those goals.
A start might be something like:
$Today = Get-Date
$ReportDate = $Today.AddDays( -7 )
$OutputCsv = ("C:\Temp\" + $Today.ToString( "yyyy-MM-dd" ) + ".csv" )
$BootTimes =
Get-WmiObject Win32_OperatingSystem -ComputerName pyex06 |
Select-Object PSComputerName, #{ LABEL='LastBootUpTime' ;EXPRESSION={ $_.ConverttoDateTime($_.lastbootuptime) } }
$BootTime |
Where-Object{ $_.LastBootUpTime -lt $ReportDate } |
Export-Csv -Path $OutputCsv -NoTypeInformation
Note: I didn't test this just hammered it out quickly for demonstration.
What's happening:
Get the current date.
Use the current date to calculate the date boundry earlier than which you want to report on.
Use the data again to derive a string suitable for naming a somewhat unique output file.
Do the query, add the LastBootUpTime property. Note: I made several syntax corrections there.
Now run the results through a Where{} statement that will filter for boot times more than 7 days old, and export to a CSV file.
Again this is just a start. You will need to add an ability to work against more than one computer, You'll need to think about what your input is going to be for that. You will probably also want to add error handling as WMI (and later CIM) connections can and do fail.
An aside: Get-WMIObject is deprecated. It's been replaced by Get-CimInstance. GetCimInstance will return a LastBootUpTime property as an actual date, with no need to add the property. That command would look something like?
Get-CimInstance Win32_OperatingSystem -ComputerName ComputerName |
Select-Object PSComputerName,LastBootUpTime
All of these things are very well documented. In fact I'm sure somebody has even published a script doing exactly what you want. You have to have some idea of how building a larger script will progress and hopefully I've given you that much. It's a series of solving individual issues and putting those solutions together etc.

BTW, the WMI stuff is depreciated, and CIM is the new hotness going forward. Note that win using cross-platform PowerShell (PowerShell Core), the WMI cmdlets do not exist.
$PSVersionTable.PSVersion
# Results
<#
Major Minor Build Revision
----- ----- ----- --------
5 1 19041 1
#>
(Get-Command -Name '*wmi*').Count
# Results
<#
21
#>
$PSVersionTable.PSVersion
# Results
<#
Major Minor Patch PreReleaseLabel BuildLabel
----- ----- ----- --------------- ----------
7 0 3
#>
(Get-Command -Name '*wmi*').Count
# Results
<#
0
#>
Secondly, the command you posted is not syntactically correct and thus would never work.
Lastly, always do the basic stuff first to make sure you are getting what you'd expect before taking the next step.
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -Property '*'
# Results
<#
...
CSName : Lab01
...
Description :
InstallDate : 20-Jun-20 18:59:14
...
Distributed : False
LastBootUpTime : 13-Aug-20 01:00:42
LocalDateTime : 20-Aug-20 14:01:53
...
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
#>
Note, you can get the dates directly with no conversions needed, then format as needed.
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -Property CSName, LastBootUpTime,
#{
Name = 'TimeSpanSinceLastRestart'
Expression = {New-TimeSpan -Start $(Get-Date) -End $PSItem.LastBootUpTime }
}
# Results
<#
CSName LastBootUpTime TimeSpanSinceLastRestart
------ -------------- ------------------------
Lab01 13-Aug-20 01:00:45 -7.15:02:24.1130902
#>
As for this...
My question is how to make it run 24/7 and append new computer
... just put in in a scheduled task (on each host or on an admin workstation where you get computer names from AD or from a file) and export to either a CSV or a file using the -Append parameter.
(Get-ADComputer).Name |
ForEach{
Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $PSItem |
Select-Object -Property CSName, LastBootUpTime,
#{
Name = 'TimeSpanSinceLastRestart'
Expression = {New-TimeSpan -Start $(Get-Date) -End $PSItem.LastBootUpTime }
} |
Export-Csv -Path 'D:\Temp\SystemRebootReport.csv' -Append -NoTypeInformation -WhatIf
}
# Results
<#
What if: Performing the operation "Export-Csv" on target "D:\Temp\SystemRebootReport.csv".
#>
Just remove the -WhatIf to create the file.
Update
As for ...
[So how do I use this implicit remoting instead then?]
...this is all detailed in the Powershell Help files, see
about_Remote_Requirements - PowerShell | Microsoft Docs
'PowerShell implicit remoting active directory'
https://www.youtube.com/results?search_query=powershell+implicit+remoting+active+directory
https://www.itprotoday.com/powershell/powershell-implicit-remoting-never-install-module-again
TechNet Install RSAT for Windows 10 1809 and 1903 and 1909 -
automated

Related

Get Chrome version for remote devices

I am using PS V3.0 to check Google chrome version:
get-content -Path C:\Support\assets.txt | ForEach-Object ({get-wmiobject win32_product -ComputerName $_ | where-Object {$_.name -eq "google chrome"} |FT version})
{write-host "$_"}
Inside the txt file are the IP addresses of remote devices.
The command is working fine and gives me Chrome version, but I cannot include IP address inside the loop.
It gives me only chrome version without information to which remote device it's from. I would like to get something like this :
IP address - Chrome version
IP address - Chrome version
As far as I understand this it should be Foreach (action){do something}?
Also is there any chance to remove word "version" from the input?
Ok, you've made some very innocent beginner type mistakes, but that's fairly easily remedied.
Let's start with ft. ft is short for Format-Table. Generally speaking, you only use a Format- command when you are trying to output something. You are trying to use the results of that, not output it, so we need to drop the ft. Instead use the Select-Object cmdlet (or more commonly used is the shorter select).
get-content -Path C:\Support\assets.txt | ForEach-Object ({get-wmiobject win32_product -ComputerName $_ | where-Object {$_.name -eq "google chrome"} |Select version})
Ok, that gets you an array of objects that only have the Version property. Not super useful, especially when you wanted to know what computer each is associated with! So, that's a good lesson in general, but not very practical here. Let's move on with actually making things better!
You are making things harder than need be by piping things to a ForEach loop like that. You are making separate Get-WMIObject calls against each IP address. If we look at get-help get-wmiobject -parameter computername we can see that it accepts an array of strings. So we can make 1 call against multiple targets, which should help speed things up a bit.
$IPList = get-content -Path C:\Support\assets.txt
Get-WMIObject win32_product -ComputerName $IPList | Where{$_.Name -eq 'Google Chrome'}
That should speed up your results a bit, but what will make things a whole lot faster is to use Get-WMIObject's -Filter parameter instead of Where. The reason is that the provider is more efficient at filtering its own objects, and returning just what you want, than PowerShell is as filtering things. Also, this reduces the data sent back from the remote machines, so you are only getting the data you want from them rather than potentially hundreds of results per machine, and then parsing down to just the ones you want. Essentially you have all of the computers' processors working on your problem, instead of just yours. So let's use the -Filter parameter:
$IPList = get-content -Path C:\Support\assets.txt
Get-WMIObject win32_product -ComputerName $IPList -Filter "Name='Google Chrome'"
Ok, things should come back a whole lot faster now. So down to the last item, you want the computer name for what each version was found on. Good news, you already have it! Well, we have the actual name of the computer, not the IP address that you specified. It is not displayed by default, but each one of those results has a PSComputerName property that you can refer to. We can simply pipe to Select and specify the properties that we want:
$IPList = get-content -Path C:\Support\assets.txt
Get-WMIObject win32_product -ComputerName $IPList -Filter "Name='Google Chrome'" | Select PSComputerName,Version
That's probably going to get you results that you're happy with. If not, you can run it through a ForEach loop similarly to how you were, and format it like you specified:
$IPList = get-content -Path C:\Support\assets.txt
ForEach($IP in $IPList){
$Version = Get-WMIObject win32_product -ComputerName $IP -Filter "Name='Google Chrome'" | Select -Expand Version
"$IP - $Version"
}

Get the size to a snapshot via Powershell in Hyper-V

Good Evening, I am developing a C# too, that is supposed to help users to control VMs on a server.
Now I have created the functions to create and erase Snapshots of these VMs and want to create a function to list all Snapshots of a VM including a view selected Snapshot parameter. With:
Invoke-Command -ComputerName ServerName -ScriptBlock { Get-VMSnapshot -VMName VMName |Select-Object}
I am able to get nearly all information that are requested but the total size of the Snapshot itself.
However I could get the size with this command:
Invoke-Command -ComputerName ServerName -ScriptBlock {Get-VM -VMName VMName | gci -r -Filter *.avhd*| select #{Name="MB";Expression={$_.Length / 1Mb}}, *}
Not to get the two results together I compared the creation time of these two with a tolerance of up to 10 seconds and took the closest one. (Primitive I know), but now it becomes even more complicated.
If I now use a Snapshot to set the VM to this state, the time of the File creation changes and so my previous method stops working completely.
Why I want to know is, is there a better way to compare these two results or even better, to get the Snapshot size without such primitive comparisons?
Thanks in advance for the help.
Below one-liners should suffice your need.
1)
Get-VM | Sort Name | Get-Snapshot | Where { $_.Name.Length -gt 0 } | Select VM,Name,Created,SizeMB
2)
Get-VM | Format-Table Name, #{Label="NumSnapshots";Expression={(Get-Snapshot -VM $_ | Measure-Object).Count}}, #{Label="TotalSnapShotSizeMB";Expression={(Get-Snapshot -VM $_ | Measure-Object -Sum SizeMB).Sum}}
Hope it helps.

Get-WMIObject include computer name

I'm trying out a script to go grab installed software on servers remotely. Problem is I want it to output certain attribs including the computer name but I can't seem to figure out how to get the name inserted.
Here is what I have so far...
$servers = Get-QADComputer -SearchRoot "OU=servers,OU=mydomain:-),DC=COM" | Select Name
...which works fine of course. Then...
$servers | % {Get-WMIObject -Class Win32Reg_AddREmovePrograms} | select Displayname,Version,InstallDate,PSComputerName
... which provides the full list of software installed on all servers in that OU but the PSComputerName becomes MY COMPUTER (the computer I run the query from - not the computername of the system being queried). The goal is to have the servername the software is installed on on each line item of software. I've asked professor Google and don't seem to see anything helpful (or anything that I understand anyway).
Hope this makes sense. semi-amateur PS script writer so hopefully this is easy for you guys. Thanks in advance for your help
Your command:
Get-WMIObject -Class Win32Reg_AddREmovePrograms
Does not specify computer to query, so it just query computer command being executed on. Thus PSComputerName display MY COMPUTER, as MY COMPUTER is computer being queried. You have to specify -ComputerName parameter to Get-WMIObject cmdlet to query specific computer. And -ComputerName parameter accept array of computer names, so you can put array of computer names to it instead of using ForEach-Object cmdlet and query one computer at time.
Since the object returned from the WMI call doesn't contain the computer you made the request on, you need to include it yourself from include your ForEach-Object (%) block. You could use Add-Member to add it yourself, then do your Select-Object outside like you're doing now:
$servers | % {
Get-WMIObject -Class Win32Reg_AddREmovePrograms -ComputerName $_ |
Add-Member -MemberType NoteProperty -Name ComputerName -Value $_ -PassThru
} | select Displayname,Version,InstallDate,ComputerName
Another way is to move the Select-Object to inside the block and do it within there, by creating a new property on the fly with a hashtable:
$servers | % {
Get-WMIObject -Class Win32Reg_AddREmovePrograms -computername $_ |
Select-Object Displayname,Version,InstallDate,#{Name='ComputerName';Expression={$_}}
}

exporting Powershell Script to CSV

We are getting ready to merge our AD with another. We have about 300 computers that I'm trying to match up with who uses them so the accounts and home folders migrate correctly, and I'm trying to think of the most efficient way to get this information.
We have everyone in an inventory system (Filemaker) (and will be implementing SCCM once we migrate (thank god) ) but we had a few errors when we did our first test batch. Im looking for something I can push out through group policy (possibly?) that will give me the computer name, logged in account, and them email it to me.
So far this is what I have.
[System.Environment]::UserName
[System.Environment]::UserDomainName
[System.Environment]::MachineName
Out-File T:\TEST.txt
But the output is blank. Any idea what I'm doing wrong here? Also is there a way to have this run on multiple computers but write to the same file?
"$env:USERNAME,$env:USERDOMAIN,$env:COMPUTERNAME" | Out-File 'T:\test.txt'
will write the name and domain of the currently logged-in user as well as the hostname of the local computer to the file T:\test.txt.
Using a single file may cause conflicts due to concurrent write attempts, though. It's better to use one file per computer, like this:
"$env:USERDOMAIN\$env:USERNAME" | Out-File "T:\$env:COMPUTERNAME.txt"
Run it as a logon script (or from a logon script), e.g. like this:
powershell -ExecutionPolicy Bypass -File "\\%USERDNSDOMAIN\netlogon\your.ps1"
Get-ADComputer -Filter * -Property * | Select-Object Name | Out-File C:\outdir\machinelist.txt -NoTypeInformation -Encoding UTF8
will get you all machine names, unless you have them already. Either way, use your list of machines in
$MachineList = Get-Content -Path c:\outdir\machinelist.txt;
foreach ($Machine in $MachineList){
($Machine + ": " + #(Get-WmiObject -ComputerName $Machine -Namespace root\cimv2 -Class Win32_ComputerSystem)[0].UserName) | Out-File "C:\outdir\result.txt" -Append
}
If you change the destination directory to somewhere that all computers have access to, it can run on multiple computers. It won't email it to you but you can just grab it.
You'll need to pipe those properties into the file like..
[System.Environment]::UserName, [System.Environment]::UserDomainName, [System.Environment]::MachineName | Out-File T:\Test.txt

PowerShell Script to query and delete print jobs older than "x" days

I started putting this PowerShell Script together, the hope would be to replace some tasks that are currently carried out manually
I'm using the
get-Date.AddDays()
function
I'm using the ISE to build the script and in testing I get output if I single out the 'starttime' property, but this seems to be a catch all because the values all come up null, ideally I'd like to use the 'timesubmitted' property, but the date seems to output in an odd that I don't think is being read correctly because my queries with 'timesubmitted' always come up empty
it comes out in this format, if you do an open query
20120416030836.778000-420
here's what I have so far.
disregard the | 'format-table' function that's just so I can see if I'm getting the desired output
#Clears Old Print Jobs on Specified server
#Sets Execution Policy for Script to run
Set-ExecutionPolicy RemoteSigned -Force
#establishes variable for cutoff date
$d = Get-Date
$old = $d.AddDays(-4)
#Queries WMI and retrieves print jobs
Get-WmiObject -class win32_printjob -namespace "root\CIMV2" | where-object {$_.timesubmitted -lt
"$old"} | ft caption,document,jobid,jobstatus,owner,timesubmitted
In PowerShell every WMI instance has a ScriptMethod that you can use to convert the dates from WMI format to .NET format:
Get-WmiObject Win32_PrintJob |
Where-Object { $_.ConvertToDateTime($_.TimeSubmitted) -lt $old } |
Foreach-Object { $_.Delete() }
Just an update in case anyone is looking in 2021.
This command/syntax worked for me in 2008 R2 (PowerShell version 2.0) (I was able to piece this together from this page, as well as others).
Finds all jobs over 30 minutes and deletes them:
Get-wmiobject win32_printjob
| Where-Object {[System.Management.ManagementDateTimeConverter]::ToDateTime($_.TimeSubmitted) -lt (Get-Date).addminutes(-30)}
| ForEach-Object { $_.delete() }*