+ Post New Thread
Results 1 to 10 of 10
Scripts Thread, Automatically enumerate array name based on string contents of a line in a file in Coding and Web Development; A piece of building management software we have dumps out reports on a daily basis, but they're fairly unintelligible and ...
  1. #1


    Join Date
    Jan 2012
    Posts
    2,618
    Thank Post
    934
    Thanked 351 Times in 267 Posts
    Rep Power
    213

    Automatically enumerate array name based on string contents of a line in a file

    A piece of building management software we have dumps out reports on a daily basis, but they're fairly unintelligible and full of information that isn't required (such as still listing everything that hasn't experienced an error) so I'm writing a script to filter through it.. Now, originally I just had several nested IF statements building 5 separate arrays before emailing them, but it feels too kludgy, so yet again we have a case of Garacesh fixing what isn't broken.

    So far I have stripped out the useless junk (their address, our address, etc) and left only the errors which leaves me with a list formatted like this:
    Code:
    Ftg.	99990	TEST ENTRY 00 GOOD ENT        OK
    Ftg.	99991	TEST ENTRY 01 GOOD ENT        OK
    Ftg.	99992	TEST ENTRY 10 FAULTY E   Warning
                    ********** Error - Type 1 **********
    Ftg.	99993	TEST ENTRY 02 GOOD ENT
    Ftg.	99994	TEST ENTRY 11 FAULTY E   Warning
                    ********** Error - Type 3 **********
    Ftg.	99995	TEST ENTRY 03 GOOD ENT        OK
    Ftg.	99996	TEST ENTRY 12 FAULTY E   Warning
                    ********** Error - Type 4 **********
    Ftg.	99997	TEST ENTRY 04 GOOD ENT        OK
    Ftg.	99998	TEST ENTRY 3 FAULTY EN   Warning
                    ********** Error - Type 2 **********
    Ftg.	99999	TEST ENTRY 4 FAULTY EN   Warning
                    ********** Error - Type 5 **********
    Ftg.	00000	TEST ENTRY 05 GOOD ENT        OK
    What I'm trying to do is write a loop that will, depending on the error, add it into an array named after that error. Now, I could do it like I have been doing previously..
    Code:
    if ($Item -match '`* Type 1 `*') {
    		[array]$E1log += $ReportArray[$Counter-1] 
    	} elseif ($Item -imatch '`* Type 2`*') { 
    		[array]$E2log += $ReportArray[$Counter-1] 
    	} elseif (
    et cetera, but I much dislike having to use the same command each time with just slight tweaks (Also I'm trying to learn more things rather than use the same kind of script)

    What I was hoping to use would be something like the following:
    Code:
    [int]$Counter = 0
    ForEach ($Item in [array]$ReportArray) {
    	if ($Item -match '\*') {
    		($(($Item).ToString() -replace ' ', '' -replace '\*', '' -replace '-', '')) += ($ReportArray[$Counter-1])
    	}
    	$Counter ++
    }
    Which in theory would get the contents of $Item (which would be the error), remove all spaces, asterisks and dashes (Leaving ErrorType1, ErrorType2 etc), then (using the counter that's clocking upwards) add the previous line to an array based on that name ($ErrorType1, $ErrorType2 etc).. Later on Send-MailMessage would fire off these errors to the IT and Site Management team only this time the report would be nicely formatted by error type and not include anything that didn't error.

    As you can guess by me posting here, things aren't going quite to plan.
    The closest I've gotten would be (${($Item).ToString() -replace ' ', '' -replace '\*', '' -replace '-', '')} += ($ReportArray[$Counter-1]) (using curled brackets) but that literally creates an array called "($Item).ToString() -replace ' ', '' -replace '\*', '' -replace '-', ''" (which I can reference later by again using curled brackets) rather than actually figuring out the (Item).ToString/Replace part first and getting a name based on that.

    Anybody have any ideas?

  2. #2

    SYNACK's Avatar
    Join Date
    Oct 2007
    Posts
    11,172
    Thank Post
    868
    Thanked 2,699 Times in 2,288 Posts
    Blog Entries
    11
    Rep Power
    772

  3. #3

    LosOjos's Avatar
    Join Date
    Dec 2009
    Location
    West Midlands
    Posts
    5,452
    Thank Post
    1,439
    Thanked 1,170 Times in 798 Posts
    Rep Power
    707
    There's a Scripting Guy blog describing how to do just that here: http://blogs.technet.com/b/heyscript...owershell.aspx

  4. #4


    Join Date
    Jan 2012
    Posts
    2,618
    Thank Post
    934
    Thanked 351 Times in 267 Posts
    Rep Power
    213
    I've already tried subexpressions, and they almost work.

    ($(($Item).ToString() -replace ' ', '' -replace '\*', '' -replace '-', '')) does modify ***** Error - Type 1 ***** into ErrorType1 as it should, but what I'm trying to do is then create an array called ErrorType1 (which would be [array]$ErrorType1) and add the items that report that error to it with += ($ReportArray[$Counter-1])

    So what should happen is..

    ForEach $Item in $ReportArray
    Enter Loop
    Read first item (Open IF)
    Item does not contain an asterisk (Close IF)
    Add 1 to Counter.
    Repeat loop
    Read second item (Open IF)
    Item does contain an asterisk (Continue IF)
    Line is ***** Error - Type 3 *****. Convert that to ErrorType3. Add the previous line to an array named $ErrorType3 (Close IF)
    Add 1 to Counter

    So on and so forth.

    Now, it needs to be the previous line that's added to the array because the error comes on the line directly after the item (none of the items contain asterisks, all of the errors do, which is why I look for those), that's why the counter is required, too (there might be another way of doing the previous item, but if there is, I don't know of it yet). Items that do not report an error simply have no error line afterwards, the next line is simply the next item in the list, which is why I'm loading the list into an array and using a ForEach loop.

    So if my 5 errors were "** Seagull in jet engine **", "** Cape snagged in rocket **", "** Medic has Ubercharged **", "**You were eaten by a grue **" and "** Your Dungeon Heart is being attacked **" I would end up with 5 arrays named $Seagullinjetengine, $Capesnaggedinrocket, $Medichasubercharged, $Youwereeatenbyagrue and $YourDungeonHeartisbeingattacked. This means I wouldn't have to have 5 different IF/ELSEIF statements in order to categorise each different type of error into its type. These reports aren't exactly sophisticated.

    Am I making any sense?
    Last edited by Garacesh; 15th July 2014 at 02:27 PM.

  5. #5

    LosOjos's Avatar
    Join Date
    Dec 2009
    Location
    West Midlands
    Posts
    5,452
    Thank Post
    1,439
    Thanked 1,170 Times in 798 Posts
    Rep Power
    707
    OK I'm with you, misunderstood the problem.

    I don't think I've ever actually heard of dynamically creating named variables before (that's what dynamic arrays are for) but my approach would be to go for a key-value pair type of approach.

    So, you create an empty array in PowerShell which will hold string values for your new array names; let's call it $key; and a second array called $value which will hold each separate array you want to store. Each time you parse the array name, you check if it already exists in $key and if not then you create it. Once you create the key, you then create the array with he same index in $value and finally add our new value to that array. If the key already exists in $key, we use the index to add data to the corresponding array in $value.

    I don't do a lot of PowerShell, but if I was tackling this problem, that's how I'd do it.

    There are a couple of articles here which may help (they were ones I skimmed through to make sure it's even possible to do it this way in PowerShell)

    Find the Index Number of a Value in a PowerShell Array - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs
    Easily Create and Manipulate an Array of Arrays in PowerShell - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs

    EDIT: if it's possible to do multi-dimensional arrays in PowerShell, then you could make this even leaner by having a single multi-dimension array, the first dimension of which is the key and the second the corresponding arrays of values. That would be the "proper" way to do it.
    Last edited by LosOjos; 15th July 2014 at 02:44 PM. Reason: Clarification of idea

  6. #6


    Join Date
    Jan 2012
    Posts
    2,618
    Thank Post
    934
    Thanked 351 Times in 267 Posts
    Rep Power
    213
    Unfortunately, that still isn't working

    PS M:\> $Item = "*** Error - Type 1 ***"
    PS M:\> $Item
    *** Error - Type 1 ***
    PS M:\> ($Item -replace ' ', '' -replace '\*', '' -replace '-', '')
    ErrorType1
    PS M:\> $Errorlist += $ErrorType1
    PS M:\> $Errorlist.indexof($($Item -replace ' ', '' -replace '\*', '' -replace '-', ''))
    -1
    PS M:\> $Errorlist.indexof(("$" + ($Item -replace ' ', '' -replace '\*', '' -replace '-', '')))
    -1
    PS M:\> $Errorlist.indexof($ErrorType1)
    0

    It recognises it if I reference it by it's actual name, but I can't seem to manipulate the input so that the same line could return multiple variables.
    Last edited by Garacesh; 15th July 2014 at 04:06 PM.

  7. #7

    LosOjos's Avatar
    Join Date
    Dec 2009
    Location
    West Midlands
    Posts
    5,452
    Thank Post
    1,439
    Thanked 1,170 Times in 798 Posts
    Rep Power
    707
    I'm not sure I explained it very well, plus doing a little research I found Powershell supports hash tables (designed for exactly this purpose)!

    I've put together an example for you, using a CSV as input that hopefully you can adapt. Here's the CSV:

    Code:
    ErrorType,Code
    Foo,5467
    Tango,5094
    Tango,8839
    Tango,1476
    Tango,7885
    Tango,2882
    Foo,5137
    Tango,9135
    Foo,8676
    Tango,43
    Tango,3202
    Fault,3394
    Tango,1137
    Fault,6424
    Tango,1318
    Tango,8667
    Fault,6441
    Tango,1881
    Fault,4497
    Fault,3025
    Fault,9048
    Foo,7143
    Foo,4569
    Fault,6771
    Foo,4924
    Foo,3221
    Foo,9109
    Fault,2783
    Foo,2449
    The Powershell script here will iterate through this CSV, check the "ErrorType" and see if it exists in the hash table. If it does, it adds the value from the "Code" column to the value part for that key (which is an array). If it doesn't, it creates the new key and adds the initial value to it. When it's done, you're left with a hash table containing all the various error types as keys, so you can look up any by name (e.g. to get the values for "Fault", you'd use $keyvalue["Fault"]).

    Code:
    $path = "C:\errors.csv"
    $keyvalue =@{}
    $file = Import-Csv -path $path
    
    foreach($line in $file)
    {
        if($keyvalue.ContainsKey($line.ErrorType))
        {
            $keyvalue[$line.ErrorType] += ,($line.Code)
        }
        else
        {
            $keyvalue.add($line.ErrorType, @($line.Code))
        }
    }
    
    $keyvalue
    EDIT: just thought I'd make the example complete by posting the output given from the example data above:

    Code:
    Name                           Value                                                                                 
    ----                           -----                                                                                 
    Fault                          {3394, 6424, 6441, 4497...}                                                           
    Foo                            {5467, 5137, 8676, 7143...}                                                           
    Tango                          {5094, 8839, 1476, 7885...}
    Last edited by LosOjos; 16th July 2014 at 11:15 AM.

  8. 2 Thanks to LosOjos:

    Garacesh (16th July 2014), spadam (16th July 2014)

  9. #8


    Join Date
    Jan 2012
    Posts
    2,618
    Thank Post
    934
    Thanked 351 Times in 267 Posts
    Rep Power
    213
    Bloody heck.. I'd completely forgotten about hastables. That's a great way to go.. My Evacuation List uses hashtables to pair staff member to last known location (keyfob use).. How could I forget?!

    Code:
    [array]$ReportArray = (Get-Content (Get-ChildItem -File "C:\Comet\report\*.RAP" | Sort LastWriteTime | Select -Last 1))
    $keyvalue =@{}
    [int]$Counter = 0
    ForEach ($Number in (0..15 + -14..-1)) {
    	[array]$ReportArray[$Number] = $null
    }
    foreach($line in $ReportArray)
    {
    	if ($line -match '\*') {
    		$keyvalue.add($ReportArray[$Counter-1], $line)
    	}
    	$Counter ++
    }
    
    $keyvalue | Format-Table -AutoSize
    That gives me a hashtable of results.. Lamp name on one side, error on the second. From here I can sort-table by Value and that'll certainly be on the right track! Great, I think I'm almost do- ..!

    Code:
    Ftg.    16588   2 CIRCULATION [EXR]      Warning                 ***** Battery not charging *****
    Ftg.    11826   G CIRCULATION COA 09 [   Warning                 ***** Battery not charging *****
                    ***** Check emergency lamp *****                 ***** Reading failure (1x) *****
    Ftg.    15514   G ICT AL01 [A1DE]        Warning                 ***** Check emergency lamp *****
    Lamps can have multiple errors... Didn't think about that.
    Last edited by Garacesh; 16th July 2014 at 11:28 AM.

  10. #9

    LosOjos's Avatar
    Join Date
    Dec 2009
    Location
    West Midlands
    Posts
    5,452
    Thank Post
    1,439
    Thanked 1,170 Times in 798 Posts
    Rep Power
    707
    Quote Originally Posted by Garacesh View Post
    Lamps can have multiple errors... Didn't think about that.
    Can't you get around it by using an array for the value and storing each individual error in that array? That's what I did with the CSV example as I expected you'd have multiple errors for the same lamp. Or am I missing the point? (it's not unusual!)

    Thinking about it, is it the case that you need to store each occurrence of each error type for each lamp? If so, can you set up a hash table for the lamps, then nest a second hash table for the error types in that hash table (or vice-versa depending on how you then want to access that data)? So you'd end up with something like this (purely made up I'm afraid!):

    Code:
    Lamp 1		Error Type 1		{ 1001, 1002, 1245 }
    		Error Type 2		{ 5687, 7845, 9865, 1254 }
    Lamp 2		Error Type 1		{ 7853 }
    		Error Type 2		{ 846, 9786, 2165 }
    Lamp 3		Error Type 3		{ 8743, 976, 654 }
    Last edited by LosOjos; 16th July 2014 at 11:48 AM.

  11. #10


    Join Date
    Jan 2012
    Posts
    2,618
    Thank Post
    934
    Thanked 351 Times in 267 Posts
    Rep Power
    213
    Something like that might work..
    Unfortunately, the entry to the (automatically generated) report (that I'm parsing to make it clearer) is as follows:

    Code:
    Ftg.     4867   1 CIRCULATION [HE]            OK
    Ftg.     8577   1 TEACHING GTL 16 [ADE        OK
    Ftg.     7203   1 CIRCULATION [HE]       Warning
                    ***** Check emergency lamp *****
                    ***** Reading failure (1x) *****
    Ftg.     7206   1 CIRCULATION [HE]            OK
    So unless I make a hash table for each and every lamp..
    This looks like I'm going to have to go back to nested IFs and I don't like doing that.. Grumble..


    Edit: Okay, it looks like hash tables are certainly the way to go..
    Code:
    [array]$ReportArray = (Get-Content (Get-ChildItem -File "C:\Comet\report\*.RAP" | Sort LastWriteTime | Select -Last 1))
    $Output = ("M:\Lighting Test " + ((Get-Date).ToString("dd-MM-yy")) + ".txt")
    $keyvalue = @{}
    [int]$LineCounter = 0
    $ReportArray[6..9], $ReportArray[-14..-4] | Out-File -Force $Output
    ForEach ($Number in (0..15 + -14..-1)) {
    	[array]$ReportArray[$Number] = $null
    }
    foreach($line in $ReportArray) {
    	if ($line -match '\*') {
    		$keyvalue.add((($ReportArray[$LineCounter-1]).ToString()), (($line).ToString() -replace '  ', '' -replace '\*', ''))
    	}
    	$LineCounter ++
    }
    
    $keyvalue.GetEnumerator() | Sort-Object -Property Value -Descending | Format-Table -AutoSize -Property Name -GroupBy Value -HideTableHeaders | Out-File -Append $Output
    This gets me results that look like:
    Code:
       Value: Reading failure - Check supply
    
    Ftg.    11839   1 CIRCULATION COA [FE]     Error
    Ftg.    11610   GYM PL02 [CE]              Error
    Ftg.    15375   G CIRCULATION COA [EXR     Error
    Ftg.    11950   G STAIRS [GE]              Error
    Ftg.    11612   GYM PL02 [CE]              Error
    Ftg.    14397   G EXTERNAL LIGHT [EX1P     Error
    
       Value:  Check emergency lamp 
    
    Ftg.    17601   G MUSIC ML01 [A1DE]      Warning
    Ftg.     6527   STAIR ENCLOSURE [FE]     Warning
    Ftg.    18306   1 SPECIALIST LAB 07 [A   Warning
    Ftg.    22116   G ICT AL01 [A1E]         Warning
    Ftg.    18309   1 SERVICED LAB 04 [A1D   Warning
    The only issue I'm having now is the issue of lamps with more than one error, they end up showing on the table like this:
    Code:
       Value:  Reading failure (1x) 
    
                    ***** Check emergency lamp *****
    Which is, of course, not ideal. So! I'm much closer to getting this right with my code looking much nicer than a tonne of nested IFs.
    Last edited by Garacesh; 16th July 2014 at 02:59 PM.

SHARE:
+ Post New Thread

Similar Threads

  1. Replies: 0
    Last Post: 9th September 2012, 06:00 PM
  2. Automatically set folder share based on folder name
    By edutech4schools in forum How do you do....it?
    Replies: 0
    Last Post: 10th March 2012, 08:40 AM
  3. Replies: 9
    Last Post: 6th October 2010, 03:54 PM
  4. Automatic Printer Selection - based on colour/mono/paper size?
    By Jamman960 in forum New Project Ideas
    Replies: 4
    Last Post: 23rd September 2008, 08:28 AM
  5. Script to call another based on machine name
    By originofsymmetry in forum Scripts
    Replies: 5
    Last Post: 2nd May 2008, 12:04 PM

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •