I have a csv file with the following info (for readibility I removed commas) :
box1 1.1.1.1 user password
box2 2.2.2.2 user password
box3 3.3.3.3 user password
I need to execute command with parameters from the csv file, like below :
plink -l user -pw password 1.1.1.1 lsvdisk -delim , -nohdr
and feed a file, with the box name on the start of each line.
In other words, I have ~300 storage boxes (V7k/SVC) and I need an inventory of all the UUID of LUNs. Preferably powershell.
Import-Csv and ForEach-Object are your friend here:
Import-Csv -Header Host,Ip,User,Pw foo.csv | ForEach-Object {
plink -l $_.User -pw $_.Pw $_.Ip lsvdisk -delim ',' -nohdr
}
Note that we have to enclose the , in quotes to ensure it's passed as a single parameter.¹
Within the script block of ForEach-Object $_ is the current object, which has the properties from the CSV file. We had to give the import custom headers since your file apparently doesn't have any. If that's not how your file looks, then adjust accordingly.
This will give you lots of output, of which perhaps the host name is no longer available. Depending on how the output looks, it may make sense to parse it and wrap it up into nice objects, but I don't really know how the output looks. A simple way of simply capturing output and associating it with the host name would be the following:
Import-Csv -Header Host,Ip,User,Pw foo.csv | ForEach-Object {
$output = plink -l $_.User -pw $_.Pw $_.Ip lsvdisk -delim ',' -nohdr
New-Object PSObject -Property #{
Host = $_.Host
Output = $output
}
}
To prefix each line of the output with the host name you can loop over the output again:
Import-Csv -Header Host,Ip,User,Pw foo.csv | ForEach-Object {
$host = $_.Host
plink -l $_.User -pw $_.Pw $_.Ip lsvdisk -delim ',' -nohdr | ForEach-Object {
"${host}: $_"
}
}
¹ If the command line includes lots of things that PowerShell wants to interpret itself, it's often easier to use %-- to stop parsing for the rest of the line after that, which then passes the rest verbatim to external commands.
Related
First, my PS knowledge is very basic, so know that up front.
I'm working on a basic script to search EventIDs in archived .evtx files and kick out "reports". The Where-Object queries are in .txt files stored in .\AuditEvents\ folder. I'm trying to do a ForEach on the .txt files and pass each query to Get-WinEvent.
Here's an example of how the queries appear in the .txt files:
{($_.ID -eq "11")}
The script is:
$ae = Get-ChildItem .\AuditEvents\
ForEach ($f in $ae) {
$qs = Get-Content -Path .\AuditEvents\$f
Get-WinEvent -Path .\AuditReview\*.evtx -MaxEvents 500 | Select-Object TimeCreated, ID, LogName, MachineName, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-GridView -Title $f.Name
}
This is the error:
Where-Object : Cannot bind argument to parameter 'FilterScript' because it is null.
At C:\Users\######\Desktop\PSAuditReduction\PSAuditReduction.ps1:6 char:177
+ ... e, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-G ...
+ ~~~
+ CategoryInfo : InvalidData: (:) [Where-Object], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.WhereObjectCommand
Your symptom implies that $qs is $null, which in turn implies that file .\AuditEvents\$f is empty.
However, even if it had content, you couldn't pass the resulting string as-is to the (positionally implied) -FilterScript parameter of Where-Object requires a script block ({ ... }).
You must create a script block from the string explicitly, using [scriptblock]::Create().
A simplified example:
# Simulated input using a literal string instead of file input via Get-Content
$qs = '{ 0 -eq $_ % 2 }' # Sample filter: return $true for even numbers.
# Remove the enclosing { and }, as they are NOT part of the code itself
# (they are only needed to define script-block *literals* in source code).
# NOTE: If you control the query files, you can simplify them
# by omitting { and } to begin with, which makes this
# -replace operation unnecessary.
$qs = $qs.Trim() -replace '^\{(.+)\}$', '$1'
# Construct a script block from the string and pass it to Where-Object
1..4 | Where-Object ([scriptblock]::Create($qs)) # -> 2, 4
Note:
Your code assumes that each .\AuditEvents\$f file contains just one line, and that that line contains valid PowerShell source code suitable for use a Where-Object filter.
Generally, be sure to only load strings that you'll execute as code from sources you trust.
Taking a step back:
As Abraham Zinala points out, a much faster way to filter event-log entries is by using Get-WinEvent's -FilterHashtable parameter.
This allows you to save hastable literals in your query files, which you can read directly into a hashtable with Import-PowerShellDataFile:
# Create a file with a sample filter.
'#{Path=".\AuditEvents\.*evtx";ID=11}' > sample.txt
# Read the file into a hashtable...
$hash = Import-PowerShellDataFile sample.txt
# ... and pass it to Get-WinEvent
Get-WinEvent -MaxEvents 500 -FilterHashtable $hash | ...
I would like to query all users currently logged in to my computer with powershell. My current solution looks like this:
$userObject = quser /server:localhost 2>&1 | ForEach-Object -Process { ($_ -replace '\s{2,}', ';').Trim() } | ConvertFrom-Csv -Delimiter ';'
This gives me the currently logged in users, but the language for the header column depends on the system language. So if I run the script on a system with english as system language I get the following result
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
random console 1 Active none 4/7/2021 6:52 AM
If I run the exactly same command on a german system I get the following:
BENUTZERNAME SITZUNGSNAME ID STATUS LEERLAUF ANMELDEZEIT
random console 1 Aktiv 1+02:29 07.04.2021 19:10
The same on a french system, you get it...
I also tried to replace the default headers with my custom ones by using this code
$header = 'UserName', 'SessionName', 'ID', 'State', 'IdleTime', 'LogonTime'
$userObject = quser /server:localhost 2>&1 | Select-Object -Skip 1 | ForEach-Object -Process { ($_ -replace '\s{2,}', ';').Trim() } | ConvertFrom-Csv -Header $header -Delimiter ';'
but this only changes the headers but not the language dependent "state" property, so if I would like to filter the users by the state I would still have to convert the property to the correct language.
Any help is much appreciated
Try:
chcp 65001
Before your command. The output is always in English in my case.
I have a script configured on my GPO that tracks a certain user group their logon times exported to a csv.
Now i've gotten the question, to make it show only the LAST logon time.
At the moment it just writes down every single logon time, but they would like only the last one.
Is there a way to make it overwrite it instead?
Let's say user1 logs in 3 times, i would like to only show it the last one, and that for every user in this group.
The script i have at the moment is a very simple one:
"logon {0} {1} {2:DD-MM-YYYY HH:mm:ss}" -f (Get-Date),$env:username, $env:computername >> \\server\folder\file.csv
Much appreciated if somebody could tell me if it is possible!
First of all, you are appending a line to a text file which is not a CSV file because the values aren't separated by a delimiter character.
Then also, you use the wrong order of values for the -f Format operator: While the template string clearly has the date as the last placeholder in {2:DD-MM-YYYY HH:mm:ss}, you feed that as the first value..
Please also notice that a date format is Case-Sensitive, so you need to change DD-MM-YYYY HH:mm:ss into dd-MM-yyyy HH:mm:ss
I'd advise to change the script you now have to something like:
[PsCustomObject]#{
User = $env:USERNAME
Computer = $env:COMPUTERNAME
LogonDate = '{0:dd-MM-yyyy HH:mm:ss}' -f (Get-Date)
} | Export-Csv -Path '\\server\folder\UserLogon.csv' -NoTypeInformation -Append
Then, when it comes down to reading that file back and preserving only the latest logons, you can do
$data = Import-Csv -Path '\\server\folder\UserLogon.csv' | Group-Object User | ForEach-Object {
$_.Group | Sort-Object {[datetime]::ParseExact($_.LogonDate, 'dd-MM-yyyy HH:mm:ss', $null)} |
Select-Object -Last 1
}
# output on screen
$data
# overwrite the CSV file to keep only the last logons
$data | Export-Csv -Path '\\server\folder\UserLogon.csv' -NoTypeInformation
I think one arrow > should be to overwrite a file.
There is also a command called Out-File with parameters to overwrite a file.
More information on the PowerShell documentation,
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-7.1
I must reiterate what others have commented about this not being the best approach to this problem however, given the output file that you have, you could process it like this to get the last record for a given user:
$last = #{} ; Get-Content \\server\folder\file.csv |% { $token = $_ -split ' ' ; $last.$($token[3]) = $_ }
$last
It creates a hash keyed with the username and updates it with the last line from the file. You can also access $last.foo to get the last entry for user foo.
I'd also note that your CSV isn't a CSV which makes it more difficult to process. You'd be better to use Export-CSV or at least putting the commas in. Also, while still not the best approach, you could create a file per user which you could just overwrite each time they login, thus:
new-object PSObject -Property #{ 'date'= (Get-Date); 'username'= $env:username; 'Computer' = $env:computername } | Export-CSV -Path "\\server\folder\$($env:username).csv" -NoTypeInformation
You could import everything for processing by doing:
gci \\server\folder\*.csv |% { import-csv $_ }
I would like to validate a list of DRAC IP's using racadm. I created a powershell script to go through each IP and the run the racadm to get the sysinfo. I'm very new to powershell and hoping get some assistance in get a result sent to a csv file with serverName and getsysinfo.
$dracList = import-csv .\currentDracList.csv -header("Server_Name","Ilo_drac_ip")
$results = #()
$dracList | % {
$dracIP = $_.Ilo_drac_Ip
$dracProps = #{
racadm -r $dracIP -u root -p P#ssword! Getsysinfo
}
$resultt += New-Object -TypeName psobject -Property $dracProps
}
$Result | export-csv .\dracResults.csv
Currently getting an error:
Missing '=' operator after key in hash literal.
t line:4 char:22
racadm -r <<<< $dracIP -u root -p P#ssword! Getsysinfo
I'm able to run the command one at a time, hoping to come up with a good script to run against IP's.
CSV file contains two columns, "Server_Name","Ilo_drac_ip" as mentioned in the script.
Please let me know if any other information is needed. It would be great to have the Server_Name appear in the results.
Thanks,
Here's how I do it on a Single server. Maybe this can put you a step closer to a solution.
$ipv4 = (racadm getsysinfo -4) | Select-String -Pattern "Current IP Address" |
ConvertFrom-StringData
$result = $ipv4."Current IP Address"
$result
I'm using powershell to locate an email address in a htm file and write it out to a text file.
I'm using select-string which finds the string OK, but writes the line number as well as the email address to the file.
All I want is the email address! It seems simple enough, but I can't crack it.
Here's my code:
$List_htm = Get-ChildItem -Filter *.htm
# Loop:
foreach ($htm in $List_htm)
{
# Locate recipient email address to send to:
# Regex pattern to match:
$pattern = '(^\W*.*#.*\.{1,}\w*$)'
$sel = select-string -list $htm -pattern $pattern | Select-Object Line
If ($Sel -eq $null)
{
write-host "FAILS - $htm does NOT contain $pattern"
}
Else
{
write-host "WORKS! $pattern `n$sel"
}
Write-host "end"
$EmailAddressee = $PDFFolder + "EmailAddressee.txt"
$sel | Out-File $EmailAddressee
}
However emailaddressee.txt looks like this:
Line
----
fred.bloggs#helpmeplease.com
All I want is a single line with the email address in:
fred.bloggs#helpmeplease.com
I could obviously further process this results file in powershell to get this, but I'm hoping someone can come up with a simple one stage result.
Thanks
Ian
Change the following line:
$sel = select-string -list $htm -pattern $pattern | Select-Object Line
To:
$sel = select-string -list $htm -pattern $pattern | Select-Object -ExpandProperty Line
That will ensure you write the property of the object rather than the textual representation of the object itself