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:
Related
I want to get all of the computers in a specific OU and ping them, but Im having trouble with Get-ADComputer.
code:
# Enter CSV file location
$csv = "filepath.csv"
# Add the target OU in the SearchBase parameter
$Computers = Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=mydomain,DC=com" | Select Name | Sort-Object Name
$Computers = $Computers.Name
$Headers = "ComputerName,IP Address"
$Headers | Out-File -FilePath $csv -Encoding UTF8
foreach ($computer in $Computers)
{
Write-host "Pinging $Computer"
$Test = Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue -ErrorVariable Err
if ($test -ne $null)
{
$IP = $Test.IPV4Address.IPAddressToString
$Output = "$Computer,$IP"
$Output | Out-File -FilePath $csv -Encoding UTF8 -Append
}
Else
{
$Output = "$Computer,$Err"
$output | Out-File -FilePath $csv -Encoding UTF8 -Append
}
cls
}
and im getting:
Get-ADComputer : The object name has bad syntax
At script.ps1:2 char 14
+ ... omputers = Get-ADComputer -Filter * -SearchBase "OU=Servers, ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+CategoryInfo : NotSpecified: (:) [Get-ADComputer], ADException
+FullyQualifiedErrorId : ActiveDirectoryServer:8335,Microsoft,ActiveDirectory,Management,Command.GetADComputer
ps. this code is taken from here. yes I know Im not supposed to do that but after getting this error
time after time I wanted to try a code that works.
Double check that the OU you're using as the search base is correct. This error occurs when it's off.
Apart from that, I recommend using the System.Net.NetworkInformation.Ping class. It's a lot faster than Test-Connection because you have more control over the ping timeout.
$ping = New-Object System.Net.NetworkInformation.Ping
$pingTimeutMS = 200
$computers = Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=mydomain,DC=com"
$results = $computers | Sort-Object Name | ForEach-Object {
$ComputerName = $_.Name
Write-Host "Pinging $ComputerName..."
$test = $ping.Send($ComputerName, $pingTimeutMS)
[pscustomobject]#{
"Computer" = $ComputerName
"IP Address" = if ($test.Status -eq "Success") { $test.Address } else { $test.Status }
}
}
$results | Export-Csv "filepath.csv" -Delimiter ',' -NoTypeInformation -Encoding UTF8
Not appending the lines to the CSV piecemeal feels a bit less clunky, too.
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 written the following sample script that lets me collect information for all servers in the environment. However, i do not have access to all servers and sometimes i get error that i want to catch and store in the result.csv file.
$Servers = Get-Content .\servers.txt
foreach ($Server in $Servers){
try {
Get-CimInstance Win32_OperatingSystem -ComputerName $server -ErrorAction Stop | Select-Object CSName, Caption, Version, OSArchitecture, InstallDate, LastBootUpTime | Export-Csv -append .\result.csv
} catch {
$Error[0].Exception | Export-csv -Append .\resulttest.csv
}
}
Normally the script works but when i try to save the errors i get the message:
Export-csv : Cannot append CSV content to the following file:
.\resulttest.csv. The appended object does not have a property that
corresponds to the following column: CSName. To proceed with
mismatched properties, add the -Force switch and retry. At
C:\temp\script\serverdata.ps1:10 char:26
+ $Error[0].Exception | Export-csv -Append .\resulttest.csv
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (CSName:String) [Export-Csv], InvalidOperationException
+ FullyQual
Any ideas on how to go around this?
You might try this:
$Servers = Get-Content .\servers.txt
$Servers | ForEach-Object {
$server = $_
try {
Get-CimInstance Win32_OperatingSystem -ComputerName $server -ErrorAction Stop |
Select-Object CSName, Caption, Version, OSArchitecture, InstallDate, LastBootUpTime, #{Name = 'Result'; Expression = {'OK'}}
}
catch {
# output an object with the same properties.
"" | Select-Object #{Name = 'CSName'; Expression = {$server}},
Caption, Version, OSArchitecture, InstallDate, LastBootUpTime,
#{Name = 'Result'; Expression = {'ERROR: {0}' -f $Error[0].Exception.Message}}
}
} | Export-Csv .\result.csv -NoTypeInformation
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
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"