Are nested loops and if statements allowed in PowerShell? - powershell

I could use some help with nested loops in PowerShell. I have a document which has item names. I have a folder full of images. I'm looping through the item names and testing if there are any images for that item. An item will only be listed in the document once, but it may have multiple images. For example, ItemXYZ in the doc could have ItemXYZ, ItemXYZ_1, and ItemXYZ_2 in the images folder. This code works for what I'm attempting to do:
for ($i=2; $i -le $rowCount; $i++) {
Write-Host -NoNewline ("Checking line $i of $rowCount`n")
$item = $worksheet.Cells.Item($i,1).Text #Item(row,column)
foreach ($image in $images) {
if ($item -eq $image.BaseName) {
$worksheet.Cells.Item($i,21).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
}
for ($x=1; $x -le 12; $x++) {
if ($item+'_'+$x -eq $image.BaseName) {
$z = $x+21
$worksheet.Cells.Item($i,$z).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
}
}
}
}
The first image for an item will always be just the base name so there is no need to check for the 12 underscore images if the first doesn't exist. Because of this, I want to put the nested for statement within the first if statement (updated code block below). This leads me to the issue. When I make this adjustment I do not get all the matches found by using the first block of code. Any idea why?
for ($i=2; $i -le $rowCount; $i++) {
Write-Host -NoNewline ("Checking line $i of $rowCount`n")
$item = $worksheet.Cells.Item($i,1).Text #Item(row,column)
foreach ($image in $images) {
if ($item -eq $image.BaseName) {
$worksheet.Cells.Item($i,21).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
for ($x=1; $x -le 12; $x++) {
if ($item+'_'+$x -eq $image.BaseName) {
$z = $x+21
$worksheet.Cells.Item($i,$z).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
}
}
}
}
}

I don't see anything in your code itself that would prevent it from working the way you're expecting it to. Andrew Davis is probably on the right track with his comment that some of your original matches were from the loop and not the If statement and the information you're loading into the script is a bit off. I'd say check those first.
If you're still running into issues, perhaps try putting a check variable in the If statement and use that to control the loop.
foreach ($image in $images) {
$match = $false
if ($item -eq $image.BaseName) {
$worksheet.Cells.Item($i, 21).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
$match = $true
}
if ($match = $true) {
for ($x = 1; $x -le 12; $x++) {
if ($item + '_' + $x -eq $image.BaseName) {
$z = $x + 21
$worksheet.Cells.Item($i, $z).Value = $hyperlinkchunk1 + $image.FullName + $hyperlinkchunk2 + $image.Name + $hyperlinkchunk3
Write-Host ("** MATCH **")
}
}
}
}
Obviously, having the loop inside the first If statement is preferable, but this option will work too.

Related

How to improve this Powershell script?

I wrote a powershell script that merges go pro video files if they have multiple files. It works when the videos are at the root drive i.e. C:, but otherwise not. The mergevideos.txt file is not created if I run the script from a different directory. Or the txt is created but it's empty. Not sure what's going on when run from a different directory.
So is there a way to fix these issues and refactor this code to make it better? Ideally I want the script to automatically look at the directory it's in or allows me to specify the directory it should work in so I can just call it from the same location but the videos can be anywhere.
$path = "C:/NewVideos/"
$oldvids = Get-ChildItem -Path $path *.mp4
foreach ($oldvid in $oldvids) {
$curpath = $oldvid.DirectoryName
$name = [System.IO.Path]::GetFileNameWithoutExtension($oldvid)
$ext = [System.IO.Path]::GetExtension($oldvid)
if ($name.StartsWith("GX01") -and !($name.EndsWith("_merged")))
{
$newvid = $curpath + $name + "_merged" + $ext
if ([System.IO.File]::Exists($newvid))
{
Write-Output "$name | ALREADY MERGED"
continue
}
$count = 1
for ($num = 2; $num -lt 10; $num++)
{
$nextpart = $name.Replace("GX01", "GX0" + $num)
if ([System.IO.File]::Exists($curpath + "/" + $nextpart + $ext))
{
$count = $num
}
}
if ($count -eq 1)
{
Write-Output "$name | SINGLE VIDEO"
continue
}
$mergefile = $curpath + "mergevideos.txt"
if (!(Test-Path $mergefile))
{
New-Item -path $curpath -name mergevideos.txt -type "file"
}
Clear-Content $mergefile
for ($num = 1; $num -le $count; $num++)
{
$nextpart = $name.Replace("GX01", "GX0" + $num)
$videofilename = "file '" + $nextpart + $ext + "'"
Add-Content $mergefile $videofilename
}
Write-Output "$name | MERGING $count VIDEOS"
ffmpeg -f concat -safe 0 -i $mergefile -c copy $newvid
}
}

Why Write-Verbose messages don't appear from conditions

I'm trying to receive Verbose messages of every sorting step and run my script with -Verbose key, but I don't receive any verbose message from the conditionals (loops). I receive my messages only if I call them outside the conditionals. Please help
[CmdletBinding()]
param()
set-location "\test folder"
$listOfItems = Get-ChildItem | Select-Object -Property Name, Mode, LastWriteTime, #{label = "FileSize (MB)";expression = {$_.Length/1024/1024}}
$bufferItem = 0
for ($i = $listOfItems.Length - 1; $i -eq 0; $i--) {
for ($j = 0; $i -lt $i; $j++) {
if ($listOfItems[$j]."FileSize (MB)" -gt $listOfItems[$j + 1]."FileSize (MB)") {
Write-Verbose -Message "Transfer the value of the buffer variable to the element below the list"
continue
}
elseif ($listOfItems[$j]."FileSize (MB)" -lt $listOfItems[$j + 1]."FileSize (MB)") {
$bufferItem = $listOfItems[$j]
$listOfItems[$j] = $listOfItems[$j + 1]
$listOfItems[$j + 1] = $bufferItem
}
Write-Verbose -Message "Transfer the value of the buffer variable to the element below the list"
}
}
There's a bug in your inner loop condition:
for ($j = 0; $i -lt $i; $j++) {
Since $i -lt $i will never evaluate to $true, the inner loop body won't ever execute, and no Write-Verbose statement is ever reached.
Fix the loop condition and you'll find Write-Verbose works perfectly fine in conditional statements.

Powershell foreach loop through a list that is split from user input

I have this here for loop:
$fromInput = 1
$toInput = 99
for ($i = $fromInput; $i -le $toInput; $i++) {
$destinationDir = '\\pc' + '{0:d5}' -f #($i) + "\$shareName\$dupDir"
$netUseDir = '\\pc' + '{0:d5}' -f #($i) + "\$shareName"
$passObj = 'pass#' + '{0:d3}' -f #($i)
}
So it would loop through PC's from 1 to 99 but what I need now is to loop through a list of numbers that User inputs that are split
I am trying to do that with a foreach loop but it is not working for me like the one in the for loop:
$userInput = Read-Host "Input numbers divided by a comma [", "]"
$numberList = $userInput.split(", ")
foreach ($i in $numberList) {
$destinationDir = '\\pc' + '{0:d5}' -f #($i) + "\$shareName\$dupDir"
$netUseDir = '\\pc' + '{0:d5}' -f #($i) + "\$shareName"
$passObj = 'pass#' + '{0:d3}' -f #($i)
}
How do I make a foreach loop that takes the $userInput, splits it into $numberList and then loops for each of the numbers in the $numberList the way it is shown above.
I much appreciate your help as always!
The main issue is that you are applying formatting (d5) to a string that is intended for an integer type. You can simply cast to [int] to get the desired result.
foreach ($i in $numberList) {
$destinationDir = '\\pc' + '{0:d5}' -f [int]$i + "\$shareName\$dupDir"
$netUseDir = '\\pc' + '{0:d5}' -f [int]$i + "\$shareName"
$passObj = 'pass#' + '{0:d3}' -f [int]$i
}
Read-Host reads data as a [string]. If that data needs to be a different type for whatever reason, a conversion will be needed whether that be implicit or explicit.
First, for user input, I recommend you use something like this:
$userInput = Read-Host "Input numbers divided by a comma [", "]"
try
{
[int[]]$numberList = $userInput.split(',')
}
catch
{
'Input only numbers separated by commas.'
break
}
To explain why [int[]] is there and why a try {...} catch {...} statement:
We are attempting to convert a string into an array and convert the resulting elements into int. As a result we should be getting an array of integers, if this was not the case, meaning if the user inputs something different than numbers separated by commas, we would get an error which is captured and handled by the catch block.
In this case, converting the strings to integers is needed by showing you a simple example:
PS /> '{0:d5}' -f '1' # String formatting on a string
1
PS /> '{0:d5}' -f 1 # String formatting on an integer
00001
Now for looping, here are the 3 easy alternatives I see:
With for loop:
for($i=$numberList[0];$i -le $numberList.Count;$i++)
{
$destinationDir = "\\pc{0:d5}\$shareName\$dupDir" -f $i
$netUseDir = "\\pc{0:d5}\$shareName" -f $i
$passObj = 'pass#{0:d3}' -f $i
}
With foreach loop:
foreach($i in $numberList)
{
$destinationDir = "\\pc{0:d5}\$shareName\$dupDir" -f $i
$netUseDir = "\\pc{0:d5}\$shareName" -f $i
$passObj = 'pass#{0:d3}' -f $i
}
With ForEach-Object loop:
$numberList | ForEach-Object {
$destinationDir = "\\pc{0:d5}\$shareName\$dupDir" -f $_
$netUseDir = "\\pc{0:d5}\$shareName" -f $_
$passObj = 'pass#{0:d3}' -f $_
}

How to remove the last comma in powershell?

Below is my script and I want to pass the value if not null to my $weekDays variable in comma separated format but I want to remove the last comma, so please help me on this.
$a = "sun"
$b = "mon"
$c = $null
$d = $a,$b,$c
$weekDays = $null
Foreach ($i in $d)
{
if ($i)
{
$weekDays = $i
$weekDays = $weekDays + ","
Write-Host "$weekDays"
}
}
Output: sun,mon,
I want: sun, mon
No need to loop through the list yourself since there's already the -join operator for that purpose, but you need to remove the null elements first
($d | Where-Object { $_ -ne $null }) -join ", "
Where-Object (alias where) will filter out the null elements.
If you just want to exclude the last null item then use this
$d[0..($d.Length - 2)] -join ", "
Note that your code produces the below output
sun,
mon,
and not sun,mon, in the same line. To print with new lines like that you need to use
($d | where { $_ -ne $null }) -join ",`n"
$a = "sun"
$b = "mon"
$c = $null
$d = $a,$b,$c
$weekDays = $null
Foreach ($i in $d)
{
if ($i)
{
if (-not ([string]::IsNullOrEmpty($weekDays)))
{
$weekDays += ","
}
$weekDays = $weekDays + $i
}
}
Write-Host "$weekDays"
##Output : sun,mon, I want : sun, mon
Your variable is null at start and you want to prepend a comma in all subsequent cases, that is, when the variable is no longer null.

Convert Array of Numbers into a String of Ranges

I was asking myself how easily you could convert an Array of Numbers Like = 1,2,3,6,7,8,9,12,13,15 into 1 String that "Minimizes" the numbers, so Like = "1-3,6-9,12-13,15".
I am probably overthinking it because right now I don't know how I could achieve this easily.
My Attempt:
$newArray = ""
$array = 1,2,3,6,7,8,9,12,13,15
$before
Foreach($num in $array){
If(($num-1) -eq $before){
# Here Im probably overthinking it because I don't know how I should continue
}else{
$before = $num
$newArray += $num
}
}
This should working, Code is self explaining, hopefully:
$array = #( 1,2,3,6,7,8,9,12,13,15 )
$result = "$($array[0])"
$last = $array[0]
for( $i = 1; $i -lt $array.Length; $i++ ) {
$current = $array[$i]
if( $current -eq $last + 1 ) {
if( !$result.EndsWith('-') ) {
$result += '-'
}
}
elseif( $result.EndsWith('-') ) {
$result += "$last,$current"
}
else {
$result += ",$current"
}
$last = $current
}
if( $result.EndsWith('-') ) {
$result += "$last"
}
$result = $result.Trim(',')
$result = '"' + $result.Replace(',', '","') +'"'
$result
I have a slightly different approach, but was a little too slow to answer. Here it is:
$newArray = ""
$array = 1,2,3,6,7,8,9,12,13,15
$i = 0
while($i -lt $array.Length)
{
$first = $array[$i]
$last = $array[$i]
# while the next number is the successor increment last
while ($array[$i]+1 -eq $array[$i+1] -and ($i -lt $array.Length))
{
$last = $array[++$i]
}
# if only one in the interval, output that
if ($first -eq $last)
{
$newArray += $first
}
else
{
# else output first and last
$newArray += "$first-$last"
}
# don't add the final comma
if ($i -ne $array.Length-1)
{
$newArray += ","
}
$i++
}
$newArray
Here is another approach to the problem. Firstly, you can group elements by index into a hashtable, using index - element as the key. Secondly, you need to sort the dictionary by key then collect the range strings split by "-" in an array. Finally, you can simply join this array by "," and output the result.
$array = 1, 2, 3, 6, 7, 8, 9, 12, 13, 15
$ranges = #{ }
for ($i = 0; $i -lt $array.Length; $i++) {
$key = $i - $array[$i]
if (-not ($ranges.ContainsKey($key))) {
$ranges[$key] = #()
}
$ranges[$key] += $array[$i]
}
$sequences = #()
$ranges.GetEnumerator() | Sort-Object -Property Key -Descending | ForEach-Object {
$sequence = $_.Value
$start = $sequence[0]
if ($sequence.Length -gt 1) {
$end = $sequence[-1]
$sequences += "$start-$end"
}
else {
$sequences += $start
}
}
Write-Output ($sequences -join ",")
Output:
1-3,6-9,12-13,15