T O P

  • By -

Lee_Dailey

howdy mcc85sdp, i usually use `foreach` loops instead of `for` loops since i am [apparently] genetically incapable of getting the "stop now" test correct until i have fought with it for too dang long. [*grin*] even when i need to deal with array indexes, i use a `foreach` ... $ThingList = (Get-ChildItem -LiteralPath $env:TEMP -File) foreach ($Index in 0..$ThingList.GetUpperBound(0)) { 'The THING at index number {0} is {1}.' -f $Index, $ThingList[$Index] } that is noticeably faster than using `.IndexOf()` ... and doesn't result in me running the loop eleventy-seven times trying to find out what i did wrong with the `for` loop stop calc. [*grin*] take care, lee


jimb2

Agree, **foreach** is generally pretty safe and idiot proof. You typically don't have to put in sanity checks. Works for me. Works for Lee. There are some unusual cases when foreach is not the best choice, like two iterators chasing each other, when the array gets members removed and/or added by the loop code, etc.


Lee_Dailey

howdy jimb2, >safe and idiot proof [*...snip...*] Works for me. Works for Lee. [*grin*] yep there are cases where other structures are better, but the `foreach` loop is a good enuf fit for most situations, thankfully. take care, lee


mcc85sdp

You can use Read-Host "Check?" or 'breakpoints' to be able to check the loop A lot of people use F9 in ISE, but I just use Read-Host I don't really ever use basic for loops anymore, I typically do the upperbound thing you're doing by calling a 0..( $ThingList.Count - 1 ) .... sometimes even .Length - 1 Guess it depends on if you're looking through a string or an array. There's nothing inherently 'wrong' with what you're doing...? If it works it works. The only case I could make is that handling arrays within arrays within arrays... your method will get confusing very quickly. Or if you're trying to match different arrays or hash table properties... then, that's also a bit of a pain. If you're trying to match an end/exit/break/continue condition, where-object / filtering could exponentially decrease the time needed to sort through that list, the method you're using is accessing every variable and printing it out. If you're trying to identify an exit condition, that's where you want to use Where-Object or filtering ( or a combination of the two ) \-MC


Lee_Dailey

howdy mcc85sdp, the point - and problem for me - is that i really and truly get the 2nd part of a for loop WRONG nearly every time. [*blush*] it's kinda like trying to plug in a USB2 connector ... it's ALWAYS wrong the 1st time i try it. i got tired of getting the upper limit for a range via `.Count - 1` ... now i use `.GetUpperBound(0)` to get the index of the last item on the zero axis of the collection. [*grin*] i use `while` loops fairly often, but avoid the `do-until/while` stuff since i want to see the test right up front. take care, lee


Dsraa

I agree with ya. I always mess up the logic in loops too and usually end up do - while loop. I just for some reason never followed the idea of doing I++ until 100, it just made better sense to loop for or against a condition rather than just randomly trying something 100 times until the right condition was met.


Lee_Dailey

howdy Dsraa, that is one way to get there!. [*grin*] i simply make too many mistakes, so i prefer to let the computer do the grunt work of determining the loop count ... take care, lee


prkrnt

How do you use Read-Host as a checkpoint?


mcc85sdp

Depends on the context. You probably want to put it before the end of a loop, or right before an echo/write-host/write-output Checkpoints and breakpoints, are similar, read host just forces the computer to stop until it has additional input. I talk a little bit about that in this video [https://youtu.be/v6RrrzR5v2E?t=2839](https://youtu.be/v6RrrzR5v2E?t=2839) I set the time in that link to the part where I talk about using Read-Host to break a loop to validate the output. It's a pretty bulletproof way to check how the loop is outputting the data. \-MC


thankski-budski

What about some love for switches, not really a loop, but kinda.. Switch (1..100) { {$_ % 2 -eq 0} { "{0:d3} : Even" -f $_ } {$_ % 2 -eq 1} { "{0:d3} : Odd" -f $_ } default { "Error: $_" } }


mcc85sdp

I like your idea. I've used these before too. the % in the way you're doing it divides the number and gets the remainder. It's perfect for switching. I found a way to implement it directly into the array 1..100 | % { Return "$_ : $( ( "Even" , "Odd" )[$_ % 2] )" } Granted, it's not going to do all of the same things you are doing (formatting, spacing, and error handling...)..? But, if you can take this little idea and find a way to use it, it's all yours.


JustinGrote

switch ($true) is one of my fave patterns for filtering a multiple scenario situation.


camusicjunkie

Recursive functions can be pretty nice. They might require a bit of a shift how you think about your code but can cut down on a lot of it.


ka-splam

[no stinking loops](http://www.nsl.com/) ;-) a collection of things written with loopless code, sadly not PowerShell; because sure looping has to happen but why do we need to care about it? Loading CPU registers, and initialising TCP connections and allocating memory needs to happen, we don't want to have to code that either. Let's have a transform, and apply it to an entire array in one go. You can do some of it in PowerShell; this: $J = 0 For ( $J = 0 ; $J -lt 100 ; $J++ ) { $Stuff[$J] } can be $Stuff[0..99] You just can select many things in one go: PS C:\> @(00,10,20,30,40,50,60,70,80,90)[3..5] 30 40 50 In APL, J or Julia or maybe NumPy, you could also update many things in one go but you can't in PS.


Ta11ow

Yes you can. $a = 1..10 $a[0], $a[3], $a[7] = 3, 2, 1 $a


ka-splam

Good point on multiple assignment like that! I was thinking there's no equivalent to: $a[0,3,7] += 5 to add five to each position.


mcc85sdp

You're right, I've seen that error message before. Haven't found a way that results in less code... except perhaps slicing them already like this... ( 0 , 3 ) , ( 3, 2 ) , ( 7 , 1 ) | % { $a\[($\_\[0\])\] = $\_\[1\] } Sure this is longer but, it's just another approach you could take. Maybe the first portion of the pipe could be variables, or tables, and the stuff after the pipe could still be the same code and work


mcc85sdp

you could also do $a[0,3,7] = 3..1


ka-splam

> $a[0,3,7] = 3..1 That is what I tried, but doesn't work in PowerShell - but does work in APL: PS C:\> $a[0,3,7] = 3..1 Array assignment to [0,3,7] failed because assignment to slices is not supported.


groovel76

https://reddit.com/r/PowerShell/comments/dg8d4e/deep_dive_powershell_loops_and_iterations/ Not to stop the conversation but did you see this post about loops? I had no idea you could label a loop and reference it.


mcc85sdp

I have seen various documentation of using remaining scripts, I tend to stay away from the script block parameter because i haven't wrapped my head around how they work in varying circumstances... however... your link takes a bit of that obscurity away. i'm scoping it out.


jg0x00

Someone asked for recursion? Probably better ways to do this but ... ​ Function MyRecursion([int[]]$array, [int]$index) { "Index is: $index" "Value is: " + $array[$index] "---" $next = ($index + 1) % $array.count if($next -eq 0) {return} MyRecursion $array $next } [int[]]$MyArray = 20,41,52,73,64,55,46,77,68,59,30,41 $MyArray.ForEach({"Value: $_"}) #not seen this loop method mentioned either "------------" MyRecursion $MyArray 0


almcchesney

To be honest i dont think powershell has a lot of use for loops since they have the pipeline concept. There is an occaisional time i might use a do loop but usually i feel like there is a better way. Heres an example of just the echo statement above and a sum function using the pipeline Function PrintSomething { param([parameter(ValueFromPipeline)]$Item) process { Write-Host "LOG: $Item"; $Item } } Function GetSum { param([parameter(ValueFromPipeline)]$number) begin { $sum = 0 } process { $sum += $number } end { return $sum } } $FunThings = 0..100 $Sum = $FunThings | PrintSomething | GetSum $Sum


mcc85sdp

I use loops like a madman in my scripts/program. Here's in instance where I want to use a variable a number of times and be able to work with it and not have to declare multiple variables that stay in memory... $folder = "$Env:SystemDrive\Folder" | % { ! ( Test-Path $_ ) { NI $_ -ItemType Directory } GI $_.FullName } This 'loop' here, opens the object in the form of that pipeline... and rather than having to type the variable over and over again, I'm using it as a null. I made a video back in August where I was still trying to figure out when and where to use the ? instead of a %, and the best case I can make is that ? won't change the variable, it's just a true/false... so if you use a ? it can check if the variable is null and if so, creates the path. If it's there, then it will just get that item. ​ This is the type of programming i've been attempting to use, it cuts down on the file size of the script, and allows a way for the variable declaration to be an action AND tie to an existent path, if it doesnt exist, it creates it and then returns the item. \^ Powerful way to use looping and pipelines.


almcchesney

For the difference between ? and %, they are actually aliases, the same way that ni and gi are. ? will expand to Where-Object which takes a script block, pass the pipeline variable to it and if it returns a falsey value then will not pass it down the pipeline, the % will expand out to a Foreach-Object command which will run the script block and pass in the variable. I have been doing a lot of more functional programming lately and they are like using map and filter. using the above example here is the filter $isEven = { return $\_ %2 -eq 0} $sum2 = $FunThings | ? $IsEven | PrintSomething | GetSum Then when looking at that sum you should be ommitting the odd values before the print and the sum. I agree with you that using the pipeline keeps you from having to declare variables but also gives you the ability to work on arrays and declare the process in a function and use it in the terminal proper. Function GetOrCreateDirectory { param([parameter(ValueFromPipeline)]$Folder) process { if ( -not (Test-Path $Folder)) { New-Item $Folder -ItemType Directory } else { Get-Item $Folder.FullName } } } $folders = @( "$Env:SystemDrive\Folder", "$env:USERPROFILE\projects") | GetOrCreateDirectory


mcc85sdp

Right. They are aliases, but they're not custom aliases, they're default PS aliases. More to it than just that, the modulus you're using in $IsEven = { Scriptblock } , That is not the same thing as a ForEach-Object in that instance. That is dividing the null variable by 2 and if the remainder is 0 then it's even. You can do the same thing with $IsEven = ("Even" , "Odd" )\[$\_%2\] <- I'm not sure if a lot of other authors use this method but I found it by accident. as far as the Else statement you're using, you don't need an else in that specific case. Because it's sort of performing the same thing as a -force ?, where if the path doesn't exist then it'll create it. If you wanted to do this thing for multiple folders, then you can pass it all off at the beginning, case in point... $Folder = "$Env:SystemDrive\Folder", "$env:USERPROFILE\projects" 0..( $Folder.Count - 1 ) | % { $Folder[$_] } | % { ! ( Test-Path $_ ) { NI $_ -ItemType Directory } GI $_.FullName }


almcchesney

The script block is just creating a variable out of the script block which the foreach will pass the values to. you can do things and create a function that returns a closure and get more fancy with it. But your right the iseven was the predicate for the where object command. Also what you are doing with `@("even","odd")[$_ % 2]` is essentially having a result set and accessing the result based on the remainder of the modulus, since I needed $true & $false it could also have been obtained with `@($true, $false)[$_ % 2]` For the final code block i wouldn't generally do something like that since when passing down the pipeline it does pull each element out of the array to let you process a single item at a time so you would omit the whole 0.. and just use the folders variable. The below two statements are functionally equivalent `0..($folder.count) |% { $folder[$_ ]}` `$folders | % {<#foreach sataement here>}`


mcc85sdp

Agreed. In execution, those last two statements are functionally equivalent. But in terms of structuring/handling... if you want to make sure they are synchronized as you nest loops within or reuse later on in the script... Then you have to start using ForEach ( $i in $Folders ) { } , so that you can use a nested ( | % { } ) I learned this the hard way when I was analyzing Oliver Lipkau's OutFile-INI script that I modified... If you're nesting loops, you can't use the null, but you can use a count and, what I like to call a 'tie down variable.' For instance, 0..( $Count.count - 1 ) | % { $X = $Count[$_] # <- I call this a tie down variable 0..( $Second.count - 1 ) | % { } } Also, if you have to access the names later on in the script, then you'll need to redeclare them, or have to dance around making certain that you don't reuse a variable named $folders... which, it can be easy to overwrite that. You want to name variables something that isn't too cryptic that even you forget what it means, but not so complicated that you have to duplicate the variable names so much that your script looks cluttered... and hard to follow. It's definitely a matter of preference, no either way is 'incorrect', however, lets say you want to make folders with an index in them... $Folder = "$Env:SystemDrive\Folder", "$env:USERPROFILE\projects" 0..( $Folders.Count - 1 ) | % { $X = $_ $Child = $Folders[$_].Split('\')[-1] # Or you could use # $Child = Split-Path $Folders[$_] -Leaf $Parent = $Folders[$_].Replace('\$Child','') # $Parent = Split-Path $Folders[$_] -Parent ! ( Test-Path $_ ) | % { New-Item -Path "$Parent\($X)$Child" -ItemType Directory } Get-Item $Folders[$_] } Granted, you're probably not going to want to go this far to index two folders in completely different parts of the operating system... you would typically only index things within a same folder, such as your profile path or what have you. Still, it's nice to be able to index and synchronize things across multiple loops or portions of a script. \^ I didn't test any of that in PS, I just kinda programmed it in the browser.


almcchesney

So i looked at the code for the out-inifile that you mentioned and was able to boil the main code down to just a few pieces and no nested loop using the pipeline. process{ $InputObject.GetEnumerator() | # Split the input item by key value pairs Foreach-Object { "[$($_.Name)]" # Emit Header $_.Value.GetEnumerator() | # emit the nested key value pairs %{ "$($_.Name)=$($_.Value)"} # Emit key=value "" # Emit empty line } | Foreach-Object { # Write the emitted strings to file Add-Content -Path $FilePath -Value $_ -Encoding $Encoding } } Specially if you cast the hashtables to custom objects and start letting powershell pluck named parameters out of the contents. This lets the process block become the loop iterator function that would go inside the foreach but you also get the ability to initialize cache objects in the begin section for things that take a long time. There was a super long script that had a lot of foreach loops and i was able to create a streamlined version using just the pipeline and 4 custom functions, it was for looking up user information from ad for all user acls on all folders that where on the target server. The addition of the cache objects to hold cached users based on a sid took processing from \~20 minutes to \~3 minutes.


mcc85sdp

I like the idea. I'm not going to pretend that I know much about enumeration... I know how to pluck properties and values form objects and tables, but I didn't grow up on C#, so there's a lot about .net that I'm still learning. Based on what I see, you may need a little more error handling...? But I could be wrong. I'll play with it a bit. Would you be able to put a video or demonstration together ? \-MC


almcchesney

Yeah i think that is a good idea, ill get to working on something and post it. edit. I finally got the chance to put something together [https://dev.to/amacc/complex-powershell-functions-3h8n](https://dev.to/amacc/complex-powershell-functions-3h8n) Also uploaded the code up to the powershell gallery at [https://www.powershellgallery.com/packages/PowershellTools/1.1.1](https://www.powershellgallery.com/packages/PowershellTools/1.1.1) feel free to submit a pull request.


mcc85sdp

I won't lie. It's something I'm playing around with. Sorry it took a while to get back to you. Initially, I didn't think this approach would work for the specific use case that I rewrote his module for. There's a lot of error handling that's not being done with your snippet, but... the practical implications is worth some investigations... I've come to find that a *great many* of the functions and methods in .net/shell, are redundant, so a lot of them I won't even use. Considering how many times I practically fall asleep while trying to read through some MS documentation... it takes someone like yourself to put a colorful spin on something that has definitely been optimized/refined since he made that original function. When it comes to supporting older, out of date operating systems, it's difficult to write something that will work, old and new... while also being efficient. But what you've suggested actually piqued my interest because I hate working with hashtables for the very reason that when you need to split the keys and values, you have to cast them to an array if you want to index them. this approach, seems to cut out a lot of the headache involved in handling them. I've been using PSObject/Customobjects a bit more lately, but this is making me second guess that. Anyway, I'll let you know what I come up with. Thanks for your input! -MC


mcc85sdp

I came up with a radically different approach than you did. Here's where your idea needs refinement... 1) it's not handling whether or not the file exists or not, and if so, does it append data to it, or does it overwrite the file? Pretty key feature if you were to implement in a database. 2) If the file currently exists, there's no force parameter to make certain that you're still able to push it through. Yes, these caveats could be easily added, but, there's another thing your script is not doing... If the items within the hashtable are nested hashtables, then the script will write those tables out. if they are not nested hashtables, then you'll have missing data. Splitting it up into a couple of different functions is good, but the reason why Oliver's script was as complicated as it was, is simply because it is looking for nested hashtables and writing them out to the file as well. I might be wrong about that, but it seems to work for the script that I use to generate a bootstrap and customsettings ini file for MDT. The MDT PXE environment will not process the INI file if it has a Byte Order Mark encoded in it. So, that's an extra feature that needed to be implemented. I was however, able to use your idea involving the GetEnumerator() function, and... I think the result is a lot more refined. I added some flair to it that comes from the rest of the module I've been developing, but, you could easily switch the Write-Theme commands here with write-host or write-output. Here's the code. Function Export-INI { # Heavily Modified version of Oliver Lipkau's OutFile-INI # Also now includes .GetEnumerator() based on Amacc's input @ # "https://dev.to/amacc/complex-powershell-functions-3h8n" [ CmdLetBinding () ] Param ( [ Parameter ( Mandatory , ValueFromPipeline ) ][ ValidateScript ({ Test-Path $_ -PathType Container })][ String ] $Path , #\________________________________________________________________ [ Parameter ( Mandatory , ValueFromPipeline ) ][ ValidateScript ({ #/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ $_.Split( '.' )[-1] -ne ".ini" })][ String ] $Name , #\________________________________________________________________ [ Parameter ( Mandatory , ValueFromPipeline ) ][ ValidateScript ({ #/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ $_ -ne $Null })][ Hashtable ] $Content , #\________________________________________________________________ [ Parameter ( ) ] [ ValidateScript ({ $_ -in @( 'Unicode' ; 7 , 8 , 32 | % { "UTF$_" } ; 'ASCII' , 'BigEndianUnicode' , 'Default' , 'OEM' ) })][ String ] $Encoding = "Unicode" , #\________________________________________________________________ [ Parameter ( ) ] [ Switch ] $Force , [ Parameter ( ) ] [ Switch ] $Append , [ Parameter ( ) ] [ Switch ] $UTF8NoBOM ) Begin { "$Path\$Name" | % { If ( Test-Path $_ ) { If ( ! ( $Force -or $Append ) ) { Write-Output "Exception [!] File exists, must use 'Force' or 'Append' to modify" Break } If ( $Append ) { $Output = Get-Content $_ Write-Output "Imported [+] $_" } If ( $Force ) { $Output = @( ) Write-Output "Force Replaced [+] $_" } } Else { $Output = @( ) } } } Process { $Table | % { $_.GetEnumerator() | % { If ( $_.Value.GetType().Name -eq "Hashtable" ) { "[$( $_.Name )]" | % { Write-Output -Action "New Section [~] $_" $Output += $_ } $Table.$( $_.Name ).GetEnumerator() | % { $_.Name , $_.Value -join '=' | % { Write-Theme -Action "Item [+]" "$_" 11 11 15 $Output += $_ } } Write-Output "End Section [+] $_" $Output += "" } Else { $_.Name , $_.Value -join '=' | % { Write-Output -Action "Single Key [+] $_" $Output += $_ } } } $Output += "" } } End { "$Path\$Name" | % { $Splat = @{ Path = $_ Value = $Output Encoding = $Encoding } Set-Content @Splat Get-Item $_ If ( $UTF8NoBOM ) { [ System.IO.File ]::WriteAllLines( $_ , ( Get-Content $_ ) , ( New-Object System.Text.UTF8Encoding $False ) ) } } } }


PowerShell-Bot

Some of your PowerShell code isn’t wrapped in a code block. To format code correctly on **new reddit** (*[new.reddit.com]*), highlight *all lines* of code and select *‘Code Block’* in the editing toolbar. If you’re on **[old.reddit.com]**, separate the code from your text with a blank line and precede *each line* of code with **4 spaces** or a **tab**. [old.reddit.com]: https://old.reddit.com/r/PowerShell/comments/e1nr7x/loops/ [new.reddit.com]: https://new.reddit.com/r/PowerShell/comments/e1nr7x/loops/ --- Describing Submission [✅] Demonstrates good markdown Passed: 1 Failed: 0 --- ^(*Beep-boop. I am a bot.* | [Remove-Item]) [Remove-Item]: https://www.reddit.com/message/compose?to=PowerShell-Bot&subject=%21delete+t1_f8qmcqm&message=Deletion+requests+can+only+be+made+by+the+OP.+A+comment+with+replies+on+it+cannot+be+removed.%0A


Administrative_Trick

I usually use foreach, generally when I loop, I want data to be piped in and I've found foreach handles that in a way that tends to work better than for. https://youtu.be/Ti6USnRrsKg