Garacesh Posted November 20, 2013 Posted November 20, 2013 (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 November 20, 2013 by Garacesh
pcstru Posted November 20, 2013 Posted November 20, 2013 (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 November 20, 2013 by pcstru
pcstru Posted November 20, 2013 Posted November 20, 2013 (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.
jamesb Posted November 21, 2013 Posted November 21, 2013 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.
Garacesh Posted November 21, 2013 Author Posted November 21, 2013 (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 November 21, 2013 by Garacesh
jamesb Posted November 21, 2013 Posted November 21, 2013 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.
Garacesh Posted November 21, 2013 Author Posted November 21, 2013 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.
jamesb Posted November 21, 2013 Posted November 21, 2013 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.
Garacesh Posted November 21, 2013 Author Posted November 21, 2013 (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 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 November 21, 2013 by Garacesh
jamesb Posted November 21, 2013 Posted November 21, 2013 (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 November 21, 2013 by jamesb
Garacesh Posted November 22, 2013 Author Posted November 22, 2013 (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 November 22, 2013 by Garacesh
jamesb Posted November 22, 2013 Posted November 22, 2013 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"})
Garacesh Posted November 22, 2013 Author Posted November 22, 2013 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!
Garacesh Posted November 22, 2013 Author Posted November 22, 2013 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)
jamesb Posted November 22, 2013 Posted November 22, 2013 Ah, my fault. That's a regex error. You actually want -notmatch ".*OK"
Garacesh Posted November 22, 2013 Author Posted November 22, 2013 (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 November 22, 2013 by Garacesh
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now