Jump to content
EduGeek EdSec 2026 is Go! 27th Oct in Derby! Join us for a day of EdTech security focused talks, networking, and an evening social ×

Powershell: Not having the same command 5 times just with different variables?


Recommended Posts

Posted (edited)

I am terrible with titles explaining my need, so I'll just explain in the body as usual.

 

So we have some Emergency Lighting software that generates .RAP files that are just text files containing the results of the last test, such as if any batteries aren't charging or the emergency lamp needs checking, but it generates it as one huge report with no actual sorting - So you'll get Check lamp, check lamp, check lamp, check supply, battery not charging, check lamp, check lamp, battery voltage low, check lamp, you get the picture - this report also includes, jumbled in with it, all the lights that haven't gotten any errors. So I've written a script to do the following:

 

  • Grab the latest .RAP file, load each line as a separate entry of an array
  • Grab certain lines that are always in the same position (from top and bottom), use them to create a text file ($output, or $body if we're using the automatic emailer)
  • Set a counter at 0. Scan $array[counter] for any of the 5 error messages (which are always on the line after the lights identification)
  • If it finds '* ERROR *', write $array[counter-1] to a new array depending on which error it was
  • Once $count -eq $array.length (so each line has been checked), append the contents of each error array one-after-the-other to $output (so now the errors are in categories)
  • E-mail this file, or open it, depending on which version of the script is run (there are two versions, e-mail runs automatically, file is the manual one)

 

Thing is, I'm using the same if command 5 times just with different array names and I can't help but think there's got to be a better way of doing it.. But after spending some considerable time researching and optimising where I can, I still can't figure it out. Anybody with more experience in this care to point me in the right direction?

 

$reportarray = (Get-Content (Get-ChildItem -File "C:\Comet\report\*.RAP" | Sort LastWriteTime | Select -Last 1))
$output = "C:\Comet\Lighting Test " + [string](Get-Date).day + "-" + [string](Get-Date).month + "-" + [string](Get-Date).year + ".txt"
$counter = 0
$n = "`r`n"
$reportarray[6..9], $reportarray[-14..-4] | Out-File $output
while ($counter -ne $reportarray.count) {
if ($reportarray[$counter] -match '`* Check emergency lamp `*') {
	[array]$CELlog += $reportarray[$counter-1]
	}
if ($reportarray[$counter] -match '`*Reading failure - Check supply`*') {
	[array]$RFCSlog += $reportarray[$counter-1]
	}
if ($reportarray[$counter] -match '`* Battery not charging `*') {
	[array]$BNClog += $reportarray[$counter-1]
	}
if ($reportarray[$counter] -match '`* Battery capacity low *') {
	[array]$BCLlog += $reportarray[$counter-1]
	}
if ($reportarray[$counter] -match '`* Battery voltage low `*') {
	[array]$BVLlog += $reportarray[$counter-1]
	}
$counter ++
}
"$n$n------------- Check Emergency Lamp -------------" | Out-File $output -Append
if ($CELlog.count -eq 0) {
"None" | Out-File $output -Append
} else {
$CELlog | Out-File $output -Append
}
"$n$n-------- Reading failure - Check supply --------" | Out-File $output -Append
if ($RFCSlog.count -eq 0) {
"None" | Out-File $output -Append
} else {
$RFCSlog | Out-File $output -Append
}
"$n$n------------- Battery not charging -------------" | Out-File $output -Append
if ($BNClog.count -eq 0) {
"None" | Out-File $output -Append
} else {
$BNClog | Out-File $output -Append
}
"$n$n------------- Battery capacity low -------------" | Out-File $output -Append
if ($BCLlog.count -eq 0) {
"None" | Out-File $output -Append
} else {
$BCLlog | Out-File $output -Append
}
"$n$n------------- Battery voltage low --------------" | Out-File $output -Append
if ($BVLlog.count -eq 0) {
"None" | Out-File $output -Append
} else {
$BVLlog | Out-File $output -Append
}
Invoke-Item $output

Edited by Garacesh
Posted (edited)

MMmmm. Some quick thoughts :

Put your criteria in an array. Abstract the compare into a function.

Do your grouping with a hash of arrays, with the key as the criteria and the error lines as the elements of the array.

Use a foreach key to loop through the hash and loop through the arrays that are the values to get your output groups.

 

I suspect there maybe a more concise ... powershell way.

Edited by pcstru
Posted

(a more powershell way)

 

... I think you could use select-string with a regex to get the lines in your 5 if statements while looping through the file. You can pipe that into select object and generate calculated properties to pipe onto to sort-object with the sort based on a made up property which identifies the group. Then you just need to combine that with your header file and spit it out. Max 10 lines including your first 6. I think.

Posted

To be honest, I'd get rid of the loop to start with. You don't need it particularly.

 

[array]$CELlog = $reportarray | Where {$_ -match '`* Check emergency lamp `*'}
$RFCSlog = $reportarray | Where {$_ -match '`*Reading failure - check supply*`'}

 

And so on.

 

That should remove a fair chunk. If you'd like to change it around a bit more, you might use a single sys object with noteproperties (eventtype and message maybe) rather than five separate variables. You could then export to CSV, or stick with the Out-File and use the | Where pipeline again.

Posted (edited)

See, I was thinking of using a {$_ -match ''} (actually, I initially tried using -like.. Whoops!) but couldn't figure it out. I was trying to use ForEach-Object but couldn't figure out how to write it out.

 

It's slightly helpful to know I was initially on the right track, at least, even if I did give up eventually xD

 

Problem is, that will just get me a tonne of lines full of 'ERROR' but not giving me the actual location of the error (as the error is on the previous line), which is why I felt the need to use a looping counter (hence $reportarray[$counter] and $reportarray[$counter-1])

Edited by Garacesh
Posted
See, I was thinking of using a {$_ -match ''} (actually, I initially tried using -like.. Whoops!) but couldn't figure it out. I was trying to use ForEach-Object but couldn't figure out how to write it out.

 

It's slightly helpful to know I was initially on the right track, at least, even if I did give up eventually xD

 

Problem is, that will just get me a tonne of lines full of 'ERROR' but not giving me the actual location of the error (as the error is on the previous line), which is why I felt the need to use a looping counter (hence $reportarray[$counter] and $reportarray[$counter-1])

 

Ahh, now I see.

 

In that case you could maybe populate the starting array like so:

 

$rawreport = (Get-Content (Get-ChildItem -File "C:\Comet\report\*.RAP" | Sort LastWriteTime | Select -Last 1))
$reportarray = @()

foreach ($line in $rawreport) {
   $report = New-Object System.Object
   $report | Add-Member -MemberType NoteProperty -Name Error -Value $line
   $foreach.MoveNext()
   $report | Add-Member -MemberType NoteProperty -Name Location $foreach.current
   $reportarray += $report
}

 

Then use as you see fit.

Posted

That.. is actually a useful place to start. Unfortunately, there's no "No Error" line, so what happens with that is that sometimes the Error is in the Error column, other times it's in the Location column.

 

I might have a crack at working with that though, if for no other reason that educational purposes.

Posted

You can, of course, also make the MoveNext conditional on whether the line it's reading is what you expect to find.

 

I'm getting into this now, so if you've got a few lines of example input then I'd be happy to have a poke at it.

Posted (edited)

As much as I dislike 'being given the answer' as it may be, quite frankly I dislike the current state of my code as current.. So have at it :D I've cut it down because it's all the same and redacted information, but kept the lines in place (otherwise lines 6 to 9 and -14 to -4 would have changed) Probably been a tad over-zealous but m'eh

 

----------  
----------           
----------
----------        
----------

****** V2K Status test ALL            ******
Date: 13-11-2013
Time: 01:36:08

Site: -----------             
     -----------                         
     -----------                               
     -----------                                
     -----------      
     -----------                      

Ftg.     7152   FAUX ENTRY - GOOD LAMP        OK
Ftg.     7167   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.    14900   FAUX ENTRY - GOOD LAMP        OK
Ftg.     7663   FAUX ENTRY - GOOD LAMP        OK
Ftg.    14387   FAUX ENTRY - GOOD LAMP        OK
Ftg.    22116   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery capacity low *****
Ftg.    17704   FAUX ENTRY - GOOD LAMP        OK
Ftg.     5166   FAUX ENTRY - GOOD LAMP        OK
Ftg.    15514   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery capacity low *****
Ftg.     8893   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     5173   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     8894   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     8888   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery capacity low *****
Ftg.     7163   FAUX ENTRY - GOOD LAMP        OK
Ftg.    15375   FAUX ENTRY - BAD LAMP      Error
               *Reading failure - Check supply*
Ftg.    11914   FAUX ENTRY - GOOD LAMP        OK
Ftg.    11915   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery not charging *****
Ftg.    14899   FAUX ENTRY - GOOD LAMP        OK
Ftg.    16590   FAUX ENTRY - GOOD LAMP        OK
Ftg.    14397   FAUX ENTRY - BAD LAMP      Error
               *Reading failure - Check supply*
Ftg.    11614   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery not charging *****
Ftg.    11612   FAUX ENTRY - BAD LAMP      Error
               *Reading failure - Check supply*
Ftg.    11610   FAUX ENTRY - BAD LAMP      Error
               *Reading failure - Check supply*
Ftg.    17327   FAUX ENTRY - GOOD LAMP        OK
Ftg.    11616   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery capacity low *****
Ftg.     4819   FAUX ENTRY - GOOD LAMP        OK
Ftg.     7156   FAUX ENTRY - GOOD LAMP        OK
Ftg.     4869   FAUX ENTRY - GOOD LAMP        OK
Ftg.     4861   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     7154   FAUX ENTRY - GOOD LAMP        OK
Ftg.     4832   FAUX ENTRY - GOOD LAMP        OK
Ftg.     4837   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     7169   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery not charging *****
Ftg.     4827   FAUX ENTRY - GOOD LAMP        OK
Ftg.     9717   FAUX ENTRY - BAD LAMP    Warning
               ***** Check emergency lamp *****
Ftg.     8370   FAUX ENTRY - BAD LAMP    Warning
               ***** Battery capacity low *****
Ftg.    13650   FAUX ENTRY - GOOD LAMP        OK
Ftg.    13652   FAUX ENTRY - GOOD LAMP        OK

Luminaire(s) tested  =      591
Luminaire(s) warning =      134
Luminaire(s) error   =        6

Battery voltage low  =        0
Battery not charging =        4
Battery capacity low =        2
Check emergency lamp =      128
Reading failure(s)   =        7

Alarm settings in progress..... ON 

****** V98.4b ******************************

Edited by Garacesh
Posted (edited)

Just been playing around a bit while killing time, and cooked up the following. Whether it's more elegant or not is a matter of opinion, but I think it at least gives you options and a starting point to turn either this, or another solution, to oyur own purposes. It all goes into an array of custom system objects, and you can then deal with them however you wish. A few Where-Object cmdlets will separate out the different statuses and/or reasons into separate files easily enough.

 

$report = (Get-Content (Get-ChildItem -File "thefile" | Sort LastWriteTime | Select -Last 1))

$lamps = @()

foreach ($line in $report) {
   $lamp = New-Object System.Object
   $lamp | Add-Member -MemberType NoteProperty -Name ID -Value $null
   $lamp | Add-Member -MemberType NoteProperty -Name Status -Value $null
   $lamp | Add-Member -MemberType NoteProperty -Name Reason -Value $null

   switch -wildcard ($line) {
       "*OK" {
           $lamp.ID = $line.Substring(4,12).TrimStart()
           $lamp.Status = "OK"
           $lamps += $lamp
       }
       "*Warning" {
           $lamp.ID = $line.Substring(4,12).TrimStart()
           $lamp.Status = "Warning"
           [void]$foreach.moveNext()
           $lamp.Reason = ($foreach.Current.TrimStart(" *")).TrimEnd("*")
           $lamps += $lamp
       }
       "*Error" {
           $lamp.ID = $line.Substring(4,12).TrimStart()
           $lamp.Status = "Error"
           [void]$foreach.moveNext()
           $lamp.Reason = ($foreach.Current.TrimStart(" *")).TrimEnd("*")
           $lamps += $lamp
       }
   }
}

$lamps | Sort-Object Status, Reason

Edited by jamesb
Posted (edited)

Well that's certainly a more concise way of doing it, but it's dawned on me I don't actually care (from the logical perspective) about why It's not working, just that it's not working (it's up to the Maintenance guys to care why).. Removing the "*OK" { ... } section would sort it fine, but I figured I'd keep working on it. Learning and all..

 

So at first I tried:

foreach ($line in $report) {
   $lamp = New-Object System.Object
   $lamp | Add-Member -MemberType NoteProperty -Name ID -Value $null
   $lamp | Add-Member -MemberType NoteProperty -Name Location -Value $null	
   $lamp | Add-Member -MemberType NoteProperty -Name Error -Value $null

   switch -wildcard ($line) {
       !"*  OK" {
$lamp.ID = $foreach.Current.Substring(4,12).TrimStart()
$lamp.Location = $foreach.Current.Substring(15,22)
[void]$foreach.moveNext()
$lamp.Error = ($foreach.Current.TrimStart(" *")).TrimEnd("*")
$lamps += $lamp
       }
   }
}

and variations thereof, but that didn't work. I'm guessing it's the NOT that's throwing it.

 

So I tried to rework it a bit, using what I've learnt so far..

$lamps = @()
$lamp = New-Object System.Object
$lamp | Add-Member -MemberType NoteProperty -Name ID -Value $null
$lamp | Add-Member -MemberType NoteProperty -Name Location -Value $null	
$lamp | Add-Member -MemberType NoteProperty -Name Error -Value $null

$report | ForEach-Object (where -notmatch "*   OK") {
$lamp.ID = $foreach.Current.Substring(4,12).TrimStart()
$lamp.Location = $foreach.Current.Substring(15,22)
[void]$foreach.moveNext()
$lamp.Error = ($foreach.Current.TrimStart(" *")).TrimEnd("*")
$lamps += $lamp
}

Unfortunately, that isn't working either. Keeps throwing up "You cannot call a method on a null-valued expression" per-line (and the subsequent file is full of blank entries to the table) - also I'm guessing that [void]$foreach.moveNext() isn't going to work, either, since I'm only feeding data one-at-a-time due to '| ForEach-Object'

 

Am I on the right track, or am I just doing it completely wrong? xD

Edited by Garacesh
Posted

You're definitely on the right track - you've also nailed the error. There's a bit of an oddity with foreach and ForEach-Object when used in the pipeline.

 

foreach builds the entire dataset before running - hence why you can get away with the [void]$foreach.moveNext() while ForEach-Object passes values one at a time. In a particularly large dataset (gigabytes for example) there's a clear advantage to not holding the whole thing in memory, but in general foreach will give you more options and run substantially faster.

 

To be honest I'd just reshuffle the line a little:

 

foreach ($item in ($report | Where {$_ -notmatch "*OK"})

Posted

Ohh, bloody hell. I was trying

foreach ($item in $report) (Where {$_ -notmatch "*OK"})
foreach ($item in $report Where {$_ -notmatch "*OK"})
foreach ($item in $report Where -notmatch "*OK")

and other such variants at one point!

Posted

It doesn't seem to like that :( It doesn't throw any errors in-Editor, so it's formatted properly, but when ran it spams:

 

parsing "*OK" - Quantifier {x,y} following nothing. $scriptlocation:14 char:45

foreach {$item in ($report | Where {$_ -notmatch "*OK"})) {

 

This time I have no idea what that error means. (except the :14 char:45 bit)

Posted (edited)

That works!

 

... too well. It scans every line, right? So it includes the errors. The errors don't contain "OK", so it pulls them back (as Location) and the next line (the next light to be tested) flags as the Error. Whoops!

 

So I get what I'm after, and then I end up with:

   Error: Ftg.     1013   LocationRemoved   Warning

ID Location               Error                                           
-- --------               -----                                           
   ***** Check emergency Ftg.     1013   LocationRemoved   Warning


  Error: Ftg.     1050   LocationRemoved    Warning

ID Location               Error                                           
-- --------               -----                                           
   ***** Check emergency Ftg.     1050   LocationRemoved    Warning


  Error: Ftg.     1844   LocationRemoved    Warning

ID Location               Error                                           
-- --------               -----                                           
   ***** Check emergency Ftg.     1844   LocationRemoved    Warning


  Error: Ftg.     1846   LocationRemoved    Warning

ID Location               Error                                           
-- --------               -----                                           
   ***** Check emergency Ftg.     1846   LocationRemoved    Warning

 

Ahah xD That certainly gave me a good chuckle

 

Edit:Tried to use

$results = $report[16..-15]
foreach ($item in ($results | Where {$_ -notmatch ".*  OK" -notmatch "`*"}))

That didn't work either

Edited by Garacesh

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now



×
×
  • Create New...