I am trying to write a PowerShell script that will query all the servers in Active Directory and see the last date a Windows Update was applied.
I was having some trouble, so just to get it done, I created two scripts, one in Powershell to get the servers and the other in VBScript to query the last date. I found a this Powershell module that allows me to query the last install date, but it is extremely slow, especially on remote servers.
Here is the PS code:
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties * |
Select-Object Name | Sort-Object Name |
ForEach-Object {
Get-WUHistory -ComputerName $_.Name | Sort-Object Date,ComputerName -Descending |
Select-Object -First 1
}
Its so slow, its practically unusable.
I have some VBScript which I cobbled together that is much faster. See below:
On Error Resume Next
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile ("servers.csv", 1)
server = ""
Do Until file.AtEndOfStream
line = file.Readline
server = line
'wscript.echo server
Set objSession = CreateObject("Microsoft.Update.Session", server)
If Err.Number <> 0 Then
'WScript.Echo server & " Error: " & Err.Number & " Error (Hex): " & Hex(Err.Number) & " Source: " & Err.Source & " Description: " & Err.Description
WScript.Echo server & " Communications Error"
Err.Clear
Else
Set objSearcher = objSession.CreateUpdateSearcher
Set colHistory = objSearcher.QueryHistory(1, 1)
For Each objEntry in colHistory
Wscript.Echo server & " " & objEntry.Date
Next
End If
Loop
file.Close
Is there an easy way to get the speed of the VBScript into the Powershell code?
Here is the working Powershell code (modified again) if anyone is interested:
$ErrorActionPreference= 'silentlycontinue'
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties * | Select-Object Name |
ForEach-Object {
If (Test-Connection $_.Name -Count 1){
Get-HotFix -ComputerName $_.Name | Sort-Object InstalledOn -Descending | Select-Object -First 1
}
else {
Write-host $_.Name " Connection Error"
}
} |
Sort-Object InstalledOn
Tim Ferrill already provided you the answer but for the record you could have done something like this
$ScriptBlock = {
$hash=#{}
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$hash[$env:Computername] = $Searcher.QueryHistory(1,1) | select -ExpandProperty Date
$hash
}
Invoke-Command -ComputerName $serverlist -ScriptBlock $ScriptBlock
This would get you something like
Name Value
---- -----
Server1 5/16/2014 2:11:42 PM
Server2 4/14/2014 1:55:03 PM
Server3 5/6/2014 5:36:51 PM
Does Get-HotFix meet your needs?
Get-HotFix -ComputerName $_.Name | Measure-Object InstalledOn -Maximum
Your performance issues could be coming from Get-ADComputer. I'd do some troubleshooting to see which piece is causing you problems.
(get-hotfix -computername SERVERNAME | sort installedon)[-1] >> c:\file.txt
This will get you the last installed date. I just use some excel work to clean up the output after you run it against all your server names.
Got that command from "get-help get-hotfix -full"
Related
I am trying to create a basic script that pulls a list of computer names from a text file, then pings them, and returns true or false. I then want to output the ones that returned false to a text file so I can know which ones are not responding.
the closest I have got to what I want to is below:
$workstations = Get-Content "workstation_list.txt"
$workstations | Test-NetConnection -InformationLevel Quiet -WarningAction SilentlyContinue
However whenever I try to pipe the results anywhere all I get is the true or false.
How can I pass the original names that were in the $workstations array to show for all the ones that return false?
I have tried:
$workstations = Get-Content "workstation_list.txt"
$workstations |
Test-NetConnection -InformationLevel Detailed -WarningAction SilentlyContinue |
Select-Object computername, pingsucceeded |
if(pingsucceeded -eq False){write-output} else{continue}
with the following error:
pingsucceeded : The term 'pingsucceeded' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
At line:11 char:144
+ ... Select-Object computername, pingsucceeded | if(pingsucceeded -eq Fal ...
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (pingsucceeded:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException*
However I can't figure out how to only return the original name of the computer that is returning false when I ping it.
I then want to output it to a text file, however if I can't get it to pass the correct information to the screen It doesn't go to a file either.
Am I close or do I need to approach this a completely different way?
Thanks!
PS.this is one of my first times posting a question on stack overflow, if I need to provide information in a different way to make it easier for you to answer please provide constructive feedback so I can do better in the future.
I'd recommend using a PSCustomObject to store your results like this:
$workstations = Get-Content "workstation_list.txt"
$Result =
foreach ($ComputerName in $workstations) {
[PSCustomObject]#{
ComputerName = $ComputerName
Online = (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
}
}
$Result
This way you can use the variable $Result for further steps if needed. Output the successful ones for example
$Result | Where-Object -Property 'Online' -EQ -Value $true
Or filter the unsuccessful ones and output them to another file for example:
$Result |
Where-Object -Property 'Online' -EQ -Value $false |
Select-Object -ExpandProperty ComputerName |
Out-File -FilePath 'offline_workstation_list.txt'
There's some basic powershell that you need to learn. You can't pipe to an if statement for one thing, but you can to foreach-object:
$workstations = Get-Content "workstation_list.txt"
$workstations |
Test-NetConnection -InformationLevel Detailed -WarningAction SilentlyContinue |
Select-Object computername, pingsucceeded |
foreach-object { if($_.pingsucceeded -eq $False){write-output $_} else{continue} }
ComputerName PingSucceeded
------------ -------------
microsoft.com False
Trying something with the call operator and $input.
echo hi | & { if ($input -eq 'hi') { 'yes' } }
yes
I have a script that returns Version numbers for 3rd Party software running on our Domain. Java, chrome, etc to ensure we are up to date.
This is all working OK.
However, I am trying to use:
Get-AppxPackage -Name Microsoft.MicrosoftEdge | select-object Version
within a remote Powershell session or 'invoke-command' but it is returning no results,
the command works fine if locally, and also when RDP'd onto the remote machine.
How can i use this cmdlet remotely to check Edge is version compliant?
Many Thanks.
EDIT:
import-module activedirectory
$workstations = Get-ADComputer -Filter "OperatingSystem -like 'Windows 10 *'" -Property * | select name -ExpandProperty Name
foreach ($workstation in $workstations)
{
$session = New-PSSession -Computername $workstation
$resultofsession = Invoke-Command -Session $Session -ScriptBlock{
$Path="HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$path2 = "HKLM:\SOFTWARE\Microsoft\Internet Explorer\"
$java = Get-ItemProperty $Path | Select-Object DisplayName, DisplayVersion | where displayname -like "java*"
$chrome = Get-ItemProperty $path | Select-Object DisplayName, DisplayVersion | where displayname -ceq "Google Chrome"
$adobe = Get-ItemProperty $path | Select-Object DisplayName, DisplayVersion | where displayname -ceq "Adobe Acrobat Reader DC"
$edge = Get-AppxPackage -Name Microsoft.MicrosoftEdge | select-object Version
$ie = get-itemProperty $path2
$Object = New-Object PSObject -property #{
'chrome' = "CHROME: " + $chrome.displayversion + ","
'edge' = "EDGE: " + $edge + ","
'ie' = "IE: " + $ie.svcVersion + ","
'java' = "JAVA: " + $java.Displayversion + ","
'adobe' = "ADOBE: " + $adobe.displayversion + ","
'hostname' = hostname
}
Write-output $object
}
remove-pssession $session
write-output $resultofsession | format-table -HideTableHeaders -autosize -force | Out-File "C:\web\Version.txt" -append
}
Get-AppxPackage will return only information for the current users profile (in this case, the account running the script). You likely need to add the -AllUsers switch, but note this will return a result for each user logged in (and they may have different versions). You can use the -user parameter to specify a specific user.
AppX packages are only updated for the user profile when they log in, hence why different users can have different versions of an app on the same workstation. Assuming everything works as expected, the app should be updated when the user next logs in.
Try the following to return the version number for each user ID:
$edge = Get-AppxPackage -AllUsers -Name Microsoft.MicrosoftEdge | select-object #{N="User"; E={$_.packageUserInformation.UserSecurityId.Username}},Version
Example output:
User Version
---- -------
test 42.17127.1.0
S-1-5-18 44.17763.1.0
jacob 44.18252.1000.0
I'm new to powershell and have been learning quite a bit. But still more to go. So my code isn't the tightest.
I've created an Event-log search tool. It allows me to search via ID, error level, key word, etc. For the most part, it works, with the exception of the keywords and provider name.
Currently, when trying to search the logs for a keyword or set of keywords, the script prompts the error message:
Get-WinEvent : The specified image file did not contain a resource section
At C:\Users\Rob\Google Drive\Powershell\Get-logs.ps1:65 char:9
+ Get-WinEvent -FilterHashtable #{Logname=$Log} -ComputerName $Computer | ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The specified image file did not contain a resource section,Microsoft.PowerShell.Commands.GetWinEventCommand
Problem is, I am not understanding the ps1.65 char:9 bit. The script then continues on and get's me old irrelevant data from the logs.
Here is the code for the two area's i'm having issue. Full code at the end.
Keyword Search:
elseif ($Kwrd -gt "a"){
foreach ($Kwrd in $Kwrd)
{
Get-WinEvent -FilterHashtable #{logname=$Log} -ComputerName $Computer | where-object { $_.Message -like "*$Kwrd*" } | Sort-Object TimeGenerated -Descending | Select-Object -First $Maxnum | Format-List
}
}
Provider Name Search:
elseif ($Prov.Length -gt 1){
Get-WinEvent -FilterHashtable #{Logname=$Log} -ComputerName $Computer | Where-Object {($_.ProviderName -like "*$Prov*")} | Sort-Object TimeGenerated -Descending|Select-Object -First $Maxnum | Format-List
}
So for example, if i wanted to search the application log for the provider name System Restore, (Which I have a few in there from the Revo application i ran recently) this is what the script does.
Enter Computer or EXIT to quit: office
Enter log set to retrieve: application
Enter Instance ID or leave blank:
Enter number of logs to retrieve: 10
Enter error level or leave blank:
Search logs by keyword or leave blank:
Search by Provider or leave blank: System Restore
Get-WinEvent : The specified image file did not contain a resource section
At C:\Users\Rob\Google Drive\Powershell\Get-logs.ps1:65 char:9
+ Get-WinEvent -FilterHashtable #{Logname=$Log} -ComputerName $Computer | ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The specified image file did not contain a resource section,Microsoft.PowerShell.Commands.GetWinEventCommand
PS C:\Users\Rob>
It's the same issue for the keyword search,. Same error message. Only difference is the line number changes from 65 to 61, since the code is on line 61.
It's not perfect, but i'm learning as I go. Here's the full script. Any idea's how I can get the information from the logs without the error?
Clear-Host
while (1 -ne 2){
$Computer = $Null
$IDNum = $Null
$Lvl = $Null
$Kwrd = $Null
$Prov = $Null
Write-Host ''
$Computer = Read-Host "Enter Computer or EXIT to quit"
if ($Computer -eq "EXIT") {exit;}
$Log = Read-Host "Enter log set to retrieve"
$IDNum = Read-Host "Enter Instance ID or leave blank"
$IDNum = $IDNum.Split(',')
$MaxNum = $MaxNum = Read-Host "Enter number of logs to retrieve"
$Lvl = Read-Host "Enter error level or leave blank"
$Lvl = $Lvl.Split(',')
$Kwrd = Read-Host "Search logs by keyword or leave blank"
$Kwrd = $Kwrd.Split(',')
$Prov = Read-Host "Search by Provider or leave blank"
if ($IDNum.Length -gt 1){
foreach ($IDNum in $IDNum)
{
Get-WinEvent -FilterHashTable #{LogName=$Log; ID=$IDNum} -ComputerName $Computer | Where-Object { ($_.ID -eq "*$IDNum*")} |Sort-Object TimeGenerated -Descending | Select-Object -First $Maxnum| Format-List
}
}
elseif ($Lvl -gt 1 ){
foreach ($Lvl in $Lvl)
{
Get-WinEvent -FilterHashTable #{LogName=$Log;Level=$lvl} -ComputerName $Computer -MaxEvents $MaxNum |Select-Object -First $MaxNum | Sort-Object TimeGenerated -Descending | Format-List
}
}
elseif ($Kwrd -gt "a"){
foreach ($Kwrd in $Kwrd)
{
Get-WinEvent -FilterHashtable #{logname=$Log} -ComputerName $Computer | where-object { $_.Message -like "*$Kwrd*" } | Sort-Object TimeGenerated -Descending | Select-Object -First $Maxnum | Format-List
}
}
elseif ($Prov.Length -gt 1){
Get-WinEvent -FilterHashtable #{Logname=$Log} -ComputerName $Computer | Where-Object {($_.ProviderName -like "*$Prov*")} | Sort-Object TimeGenerated -Descending|Select-Object -First $Maxnum | Format-List
}
else {
Get-WinEvent -LogName $Log -ComputerName $Computer | Sort-Object TimeGenerated -Descending| Select-Object -First $MaxNum | Format-List
}
} else{
Clear-Host
$log = $IDNum = $MaxNum = $Lvl = $Kwrd = $Prov = $Null
continue
Write-Host ''
Write-Host ''
}
Thanks.
Note: I'm Running ISE as admin.
Couple of comments
1) I can't recreate the error (your script ran fine on my machine) but it's behaving as though its trying to open/read an image file (very odd).
2) I tried running get-winevent with no parameters and I got many get-winevent : The data is invalid errors. When I researched this error, I learned thatget-winevent seems to be a buggy/problematic/fussy cmdlet. So, I suggest you try get-eventlog instead
3) You're invoking Get-WinEvent inside a loop which makes the program take much longer to run then necessary. I suggest you execute Get-EventLog (see comment #2 above) one time and pipe the output to out-gridview. For example:
Get-EventLog -LogName application | out-gridview -Title "App log events"
Then, use the out-gridview filters to display only the output you want to see.
Example output for the command above:
I'm writing a script to get the Username of any user logged on to any computer. It works fine with 2 Exceptions.
Here is the code:
$computername = Get-Content 'I:\NodeList\SWSL.txt'
Remove-item "I:\NodeList\UsersPC.txt"
foreach ($computer in $computername)
{
if(Test-Connection -Computername $Computer -BufferSize 16 -Count 1 -ea 0)
{
$Uinfo = gwmi win32_computersystem -comp $computer | select Username,Caption #,Manufacturer
$Uinfo | Out-File -Filepath "I:\NodeList\UsersPC.txt" -Append
Write-Host $Uinfo
}else{
Write-Host $computer " Is offline, not responing to ping"
$computer + " Is offline!" | Out-File -Filepath "I:\NodeList\UsersPC.txt" -Append
}
}
First the Output is:
USername Caption
-------- -------
BINYAN\Charlie SYD-WS04
But I would only like to have:
Charlie SYD-WS04
The domain is always the same, so I just need the username and the computer name, and NOT the headings or the "---"
2nd Problem is, we are a render farm and all renders are processed on a user account Called "Render".
For those computers I only get the computer name, Username is Blank.
This user account is a Domain user, but not in an OU like the others (Sydney, Melbourne, Brisbane).
It resides in the default "Users" folder in AD.
You could remove the domain name using a regex that replaces everything until and including a \: '.*?\\'
You get rid of the heading using select -expand or using a foreach loop where you select the properties using $_.PROPERTY
So your statement could look like this:
$Uinfo = gwmi win32_computersystem -comp $computer |
foreach { '{0} {1}' -f ($_.Username -replace '.*?\\'), $_.Caption }
I have no answer for / I dont understand your second question.
I was recently tasked with keeping track of the laptops that are encrypted with BitLocker. We have over 300 laptops and not are on the network at the same time. The person that I took over for was using the flowing command line script to create a txt for each computer.
FOR /F %%A IN (c:\Temp\BitLock\BitLock.txt) DO c:\temp\BitLock\PsExec.exe \\%%A -e cmd /c (hostname ^& Date /T ^& manage-bde.exe -status ^& manage-bde -protectors c: -get) >> \\server\Bitlocker\Recovery_Key\2015\%%A.log pause
I would like to have that information in one csv so I can quickly go through to see what is not encrypted. Opening one file at a time seems like a lot of extra work. The below code is what I have come up with in PowerShell. I am having problems getting the data to show in the cells. I don't think I am using the right object class name.
$computers= get-content c:\temp\computerlist.txt
$txtfile = "c:\temp\test\Computer4.txt"
foreach ($computer in $computers){
manage-bde -cn $compute -status |
Select "Conversion Status",Password |
export-csv c:\temp\test\Computer4.csv
}
My end goal is to have it display like below.
Computer Name Recovery Key Conversion Status Protection Status Computer Description
Name XXXXXXXXXX Fully Encrypted Protection On John, Smith Laptop
I know this is a very old post but I thought I'd still contribute an answer for anyone else that comes across this.
Here's what I came up with which involved some custom formatting as PowerShell does not see manage-bde output as a PSObject:
$computers= get-content c:\temp\computerlist.txt
$txtfile = "c:\temp\test\Computer4.txt"
$bdeObject = #()
foreach ($computer in $computers) {
$bde = manage-bde -cn $computer -status
$ComputerName = $bde | Select-String "Computer Name:"
$ComputerName = ($ComputerName -split ": ")[1]
$ConversionStatus = $bde | Select-String "Conversion Status:"
$ConversionStatus = ($ConversionStatus -split ": ")[1]
$ConversionStatus = $ConversionStatus -replace '\s','' #removes the white space in this field
$PercentageEncrypted = $bde | Select-String "Percentage Encrypted:"
$PercentageEncrypted = ($PercentageEncrypted -split ": ")[1]
#Add all fields to an array that contains custom formatted objects with desired fields
$bdeObject += New-Object psobject -Property #{'Computer Name'=$ComputerName; 'Conversion Status'=$ConversionStatus; 'Percentage Encrypted'=$PercentageEncrypted;}
}
$bdeObject | Export-Csv c:\temp\test.csv -NoTypeInformation
Also I did not see a Recovery Key or Computer Description field in manage-bde. But this gets you a csv with the Computer Name, Conversion Status, and Percentage Encrypted fields.