I'm in the middle of writing a script that finds inconsistensis of the servers in our infrastructure. Basicly what i do is source the Name and status of our servers from 2 different sources and find missmatches. Source 1 is our SQL with the properties Name and Status (status can be either InUse or Removed) Source 2 is from vCenter with properties Name and PowerState (PoweredOn or PoweredOff) These are then stored in different variables,
Using this info i'd like to compare Status and PowerState of each server, InUse and PoweredOn should match and be outputted to null while all others (for ex InUse and PoweredOff) should not match and be outputted to a table or csv along with the servername for further investigation.
I tried renaming the properties to the same name and then filtering them with ForEach but i canĀ“t quite get it to work.
Any help is appreciated.
Assumptions:
You're using PowerShell
I'm assuming that you know how to fetch the lists into a dictionary. For sake of ease, I'll assume you're using hashtables.
I'm assuming that the names of the servers are exactly the same in both sources. If not, you will need to account for this in your script (lookup table perhaps) or adjust the names in one source.
$sqllist is the list from SQL, keyed by name
$vclist is the list from vCenter, keyed by Name
$output = #()
foreach ($serversql in $sqllist)
{
$entry = $null
$servervc = $vclist.Get($serversql.Name)
if ($serversql.Status -match 'InUse' `
-and $servervc.PowerState -match 'PoweredOn')
{
$entry = [PsCustomObject]#{
Name=$serversql.Name
SqlStatus=$null
vCenterStatus=$null
}
} else
{
$entry = [PsCustomObject]#{
Name=$serversql.Name
SqlStatus=$serversql.Status
vCenterStatus=$servervc.PowerState
}
}
}
$output | Export-CSV -Path log.csv
Related
I am trying to figure out who has been printing from a 2008 print server. I generated a log file from server manager and now have this information in a csv file. My goal is to parse through this info and export it to a new csv to find out the only the associates user ID, computer host name and printer name, all included in the csv log file so I can determine who is printing from this server and make sure I can map them to our new print server. The csv consists of one column of data which has a pattern.
Each row in the csv follows the below pattern, but the wording is different for each row since the job name is longer/shorter or other bits of information that I don't want.
The overall pattern is:
Document #, job name owned by **user ID** on **computer host name** was printed on **printer name** through port ********
More information I don't want
My problem is that I can't hard code something like ignore the first 5 words of each row then the 6th word would be the user ID etc, since the format is different on each row.
What is the best way for me to ignore all words up until either the phrase "owned by", or even better, user ID, save that to a new csv in, then do the same thing for the computer host name and printer name?
This could be done easily enough using Regular Expression matching. Regular Expressions use pattern matching, so you could do something like:
Get-Content LogFile.csv | Where{$_ -match "^(.*?),.+? owned by (.+?) on (.+?) was printed on (.+?) through port (.+)"} | ForEach{
[PSCustomObject]#{
JobNumber=$Matches[1]
UserId=$Matches[2]
ComputerName=$Matches[3]
PrinterName=$Matches[4]
Port=$Matches[5]
}
}|Export-Csv NewLogFile.csv -NoTypeInfo
That would give you a CSV you could open in Excel or something with just the job number, user ID, the computer they used, the printer it went to, and the port it went on.
TheMadTechnician's Answer already covers a majority of this.
$a = Get-Content original.csv
$a[0] += ",Data"
$a | Set-Content updated.csv
$csv = Import-Csv updated.csv
$data = $csv.where({$_."Event ID" -eq 307}) | Select-String -pattern "(?<=owned by )(?<user>[^ ]+)(?: on )(?<host>.*?)(?: was printed on )(?<printer>.*?)(?= through )"
$(ForEach ($m in $data.matches) {
[pscustomobject][ordered]#{"User"=$m.groups["user"].value
"Host"=$m.groups["host"].value
"Printer"=$m.groups["printer"].value
}
}) | Export-Csv Output.csv -notypeinformation
There are some issues with the CSV that is exported from the Windows print server. If the numbered issues below do not matter in this case, then I can just remove this answer.
The CSV column that contains the data you care about has no name. The other columns have headers, but for some reason this one does not. Without that header, your Import-Csv command will be useless. The first four lines of the code cover adding the Data header to that file. So you can either use code to fix that or just open the file, add the column name, and save.
The event ID you care about is 307. There's a lot of other noise in that event log unless you pre-filtered it before saving it as a CSV, and that could impact the regex matching.
My method here is really no different than the other posted answer. I'm just matching less strings and access those matches with named indexes.
This is not an answer for how to extract information from the message text but rather how to avoid having to deal with formatted text in the first place. It appears you are trying to parse the message for Event Log events with ID 307. This code is adapted from PowerShell One-Liner to Audit Print Jobs on a Windows based Print Server.
Using the Get-WinEvent cmdlet you can query a specific log (Microsoft-Windows-PrintService/Operational) for specific events (307), and then it's just a matter of retrieving and adding a meaningful name to each property...
$eventFilter = #{
LogName = 'Microsoft-Windows-PrintService/Operational';
ID = 307;
}
Get-WinEvent -FilterHashTable $eventFilter `
| Select-Object -Property `
'TimeCreated', `
#{ Name = 'JobId'; Expression = { $_.Properties[0].Value }; }, `
#{ Name = 'JobName'; Expression = { $_.Properties[1].Value }; }, `
#{ Name = 'UserName'; Expression = { $_.Properties[2].Value }; }, `
#{ Name = 'MachineName'; Expression = { $_.Properties[3].Value }; }, `
#{ Name = 'PrinterName'; Expression = { $_.Properties[4].Value }; }, `
#{ Name = 'PortName'; Expression = { $_.Properties[5].Value }; }, `
#{ Name = 'ByteCount'; Expression = { $_.Properties[6].Value }; }, `
#{ Name = 'PageCount'; Expression = { $_.Properties[7].Value }; }
For an event with a message like this...
Document 1, Print Document owned by UserName on \\MachineName was
printed on Microsoft Print to PDF through port X:\Directory\File.ext.
Size in bytes: 12345. Pages printed: 1. No user action is required.
...the above code will output an object like this...
TimeCreated : 3/28/2019 5:36:41 PM
JobId : 1
JobName : Print Document
UserName : UserName
MachineName : \\MachineName
PrinterName : Microsoft Print to PDF
PortName : X:\Directory\File.ext
ByteCount : 12345
PageCount : 1
You could pipe the above command into Export-CSV to create your CSV file, or even just use Out-GridView to view and filter the data directly in PowerShell. Either way, no parsing necessary.
I have two array's, one contains multiple columns from a CSV file read in, and the other just contains server names, both type string. For this comparison, I plan on only using the name column from the CSV file. I don't want to use -compare because I want to still be able to use all CSV columns with the results. Here is an example of data from each array.
csvFile.Name:
linu40944
windo2094
windo4556
compareFile:
linu40944
windo2094
linu24455
As you can see, they contain similar server names, except $csvFile.Name contains 25,000+ records, and $compareFile contains only 3,500.
I've tried:
foreach ($server in $compareFile) {
if ($csvFile.Name -like $server) {
$count++
}
}
Every time I run this, it takes forever to run, and results in $count having a value in the millions when it should be roughly 3,000. I've tried different variations of -match, -eq, etc. where -like is. Also note that my end goal is to do something else where $count is, but for now I'm just trying to make sure it is outputting as much as it should, which it is not.
Am I doing something wrong here? Am I using the wrong formatting?
One possible thought given the size of your data.
Create a hashtable (dictionary) for every name in the first/larger file. Name is the Key. Value is 0 for each.
For each name in your second/smaller/compare file, add 1 to the value in your hashtable IF it exists. If it does not exist, what is your plan???
Afterwards, you can dump all keys and values and see which ones are 0, 1, or >1 which may or may not be of value to you.
If you need help with this code, I may be able to edit my answer. Since you are new, to StackOverflow, perhaps you want to try this first yourself.
Build custom objects from $compareFile (so that you can compare the same property), then use Compare-Object with the parameter -PassThru for the comparison. Discriminate the results using the SideIndicator.
$ref = $compareFile | ForEach-Object {
New-Object -Type PSObject -Property #{
'Name' = $_
}
}
Compare-Object $csvFile $ref -Property Name -PassThru | Where-Object {
$_.SideIndicator -eq '<='
} | Select-Object -Property * -Exclude SideIndicator
The trailing Select-Object removes the additional property SideIndicator that Compare-Object adds to the result.
I'm reading in a CSV file which contains 25,000 records, and am reading each column into a psobject. Here is what I have so far:
$file = Import-CSV .\server.csv
$tempobj = New-Object psobject -Property #{
'Name' = $file.Name
'Group' = $file.Group
}
When this is ran, I get the correct results I want, being that $file.Name contains all the server names, and $file.Group contains the groups for servers. However, my issue is that I need to edit the names of each server without interfering with the .Group. Here is an example of what a server name look like as is.
WindowsAuthServer #{wdk9870WIN}
I need to remove WindowsAuthServer #{ and WIN} from each server name, leaving only the server name left, or for this example, wdk9870.
I tried using the -replace function ($tempobj.Name -replace "WindowsAuthServer #{",""), but it requires that I save the results to a new array, which then messes up or removes .Group entirely
Is there a different way to go about doing this? I'm lost.
Suppose your server.csv looks like this:
"Name","Group"
"WindowsAuthServer #{wdk9870WIN}","Group1"
"WindowsAuthServer #{wdk9880WIN}","Group2"
"WindowsAuthServer #{wdk9890WIN}","Group1"
"WindowsAuthServer #{wdk9900WIN}","Group1"
And you want to change the values in the Name column only, then something like this would probably do it:
Import-Csv .\server.csv | ForEach-Object {
New-Object psobject -Property #{
'Name' = ([regex]'#\{(\w+)WIN\}').Match($_.Name).Groups[1].Value
'Group' = $_.Group
}
}
This will output:
Name Group
---- -----
wdk9870 Group1
wdk9880 Group2
wdk9890 Group1
wdk9900 Group1
If you want, you can simply pipe this info to the Export-Csv cmdlet to save as a new CSV file. For that, just append | Export-Csv -Path .\server_updated.csv -NoTypeInformation to the code.
Hope that helps
I am importing data from various csv files, usually with 4 or 5 fields.
e.g. one might look like:
id, name, surname, age
1,tom,smith,32
2,fred,bloggs,50
I have managed to grab the header row titles into and array that looks like:
id, name, surname, age
the first data row looks like:
#{ID=1; name=tom; surname=smith; age=32}
say I assign it to $myRow
what I want to be able to do is access the ID, name etc field in $myRow by index, 0, 1, 2 etc, not by the property name.
Is this possible?
Thanks
You can do something like this, but it may be slow for large sets of rows and/or properties:
$users =
import-csv myusers.csv |
foreach {
$i=0
foreach ($property in $_.psobject.properties.name)
{
$_ | Add-Member -MemberType AliasProperty -Name $i -Value $property -passthru
$i++
}
}
That just adds an Alias property for each property name in the object
When I wanted to do something similar, I went about it differently.
I used Import-Csv to get the contents into a table. Then I stepped through the table, row by row, and used an inner loop to retrieve the field values, one by one into variables with the same name as the column name.
This created a context where I could apply the values to variables embedded in some kind of template. Here is an edited version of the code.
foreach ($item in $list) {
$item | Get-Member -membertype properties | foreach {
Set-variable -name $_.name -value $item.$($_.name)
}
Invoke-expression($template) >> Outputfile.txt
}
I'm writing the expanded templates to an output file, but you get the idea. This end up working more or less the way mail merge applies a mailing list to a form letter.
I wouldn't use this approach for more than a few hundred rows and a dozen columns. It gets slow.
Explanation:
The inner loop needs more explanation. $list is a table that contains
the imported image of a csv file. $item is one row from this table.
Get-Member gets each field (called a property) from that row. Each
field has a name and a value. $_.name delivers the name of the
current field. $item.($_.name) delivers the value. Set-Variable
creates a variable. It's very inefficient to create the same
variables over and over again for each row in the table, but I don't
care.
This snippet was clipped from a larger snippet that imports a list and a template, produces an expansion of the template for each item in the list, and outputs the series of expansions into a text file. I didn't include the whole snippet because it's too far afield from the question that was asked.
You can actually index your array with ($MyRow[1]).age in order to get the age of the first row.
I am reading in values from a CSV file using Import-Csv which include server name, ip address and a note field
Is there a built in mechanism to find an object based on the server name without having to loop through the entire array?
I'm keeping the array in memory to use multiple times.
$ServerList = Import-Csv ".\ServerList.csv"
I'm creating a GUI with Powershell Studio and populating a dropdown with all the server names read in from the file. The user will choose a server from the dropdown and there will be buttons to perform actions on that server using the IP address read in.
You could populate a hashtable with the data from the CSV, using the servername as key:
$ServerList = #{}
Import-Csv '.\ServerList.csv' | % { $ServerList[$_.ServerName] = $_ }
That way you can access the data like this:
$server = 'foo'
$ServerList[$server].IPAddress
$csv = Import-csv my.csv | where {$_.servername -eq "MyServer"}
foreach ($server in $csv) {
# Do your thing
}
If you need to call it multiple times you can use a function that you call when the user selects a server:
function getServer($selectedServer){
$ServerInfo = Import-Csv ".\ServerList.csv" | where {$_.servername -eq $selectedServer}
return $ServerInfo
}
When the user makes a selection the code would like like so:
$Server = getServer('ABCSRV')
Then $Server.IP and $Server.Notes would have the information from that specific server, granted that you have these columns in place in your csv.