0

I have a script that runs through a while loop with five steps inside. If the step is completed, its variable, $Step_Required (e.g., $Step1Required), is set to false. If there is an error, a function is run giving the user the option to restart from the current step (therefore not affecting the current completed steps) or start again from the beginning of the loop by setting all the $Step_Required variables to true.

For some reason, when the loop is being reset, the variables are being changed somewhere/somehow. My current thought is there is a bug. However, the likely chances of a true bug are small, and I suspect there is something that I am unaware of (I am a relative beginner) causing the issue.

As I mentioned, I have a function that deals with providing the user options when an error is encountered.

function RetryAgain {
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Enter the step number to return to (as an integer).")]
        [int]$Step
    )
    $Choice = Read-Host "Press [R] to retry, [A] to start again, or any other key to exit"
    if ($Choice -eq 'R' -or $Choice -eq 'r') {
        continue
    }
    elseif ($Choice -eq 'A' -or $Choice -eq 'a') {
        Write-Host "[END] Returning to the beginning." -ForegroundColor Green
        Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
        $Step1Required = $true ; $Step2Required = $true ; $Step3Required = $true ; $Step4Required = $true ; $Step5Required = $true # Set-Variable -Name "Step${Step}Required" -Value $true
        Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
        continue
    }
    else { Stop-Transcript ; $VerbosePreference = "SilentlyContinue" ; exit }
}

The basic structure of the script is this:

while ($Step1Required -or $Step2Required -or $Step3Required -or $Step4Required -or $Step5Required) {
    Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
    if ($Step1Required) {
        # Step 1: Request general inputs
        Write-Host "[BEGIN] 1`: General inputs" -ForegroundColor Cyan
        # Get user inputs and save to variables
        Write-Host "[END] 1`: General inputs set" -ForegroundColor Green
        $Step1Required = $false
    }

    if ($Step2Required) {
        # Step 2: Input CSV Path
        Write-Host "[BEGIN] 2`: CSV file path input" -ForegroundColor Cyan
        try {
            # Get CSV file and import
            $Step2Required = $false
        }
        catch { 
            Write-Host "[ERROR] $_" -ForegroundColor Red
            RetryAgain 
        }
    }

    if ($Step3Required) {
        # Specific functions group
        Write-Host "[BEGIN] 3`: Function started - $TaskNumber`: $TaskDescription" -ForegroundColor Cyan
        try {
            switch ($TaskNumber) {

                # New-Team script
                "1" {
                    # Check if the task number is 1 (Create a New Team)
                    try {
                        # Get more user input and save to variables
                        # Run a function 'NewTeam'
                    }
                    catch { 
                        Write-Host "[ERROR] $_" -ForegroundColor Red
                        RetryAgain 
                    }
                }

                "2" {  }
                "3" {  }
                "4" {  }
                "5" { Stop-Transcript; $VerbosePreference = "SilentlyContinue"; exit }
                Default { Write-Host "[WARNING] Invalid choice. Please try again." -ForegroundColor Yellow ; RetryAgain }
            }
            # For all functions, check the output for success.
            if ( successful) { $Step3Required = $false }
            else {
                Write-Host "[ERROR] $_" -ForegroundColor Red
                RetryAgain 
            }
        }
        catch { 
            Write-Host "[ERROR] $_" -ForegroundColor Red
            RetryAgain 
        }
    }

    if ($Step4Required) {
        # Step 4: Display or save the $Step3Output
        Write-Host "[BEGIN] 4`: Displaying and Saving Outputs" -ForegroundColor Cyan
        # Format the output to view or save
        Write-Host "[END] 4`: Outputs displayed and saved" -ForegroundColor Green
        $Step4Required = $false
    }

    if ($Step5Required) {
        Write-Host "You have reached the end of the script."
        $Choice = Read-Host "Press [A] to start again, or any other key to exit"
        if ($Choice -eq 'A' -or $Choice -eq 'a') {
            Write-Host "[END] Returning to the beginning." -ForegroundColor Green
            $Step1Required = $true # Set-Variable -Name "Step${Step}Required" -Value $true
            continue
        }
        else { Stop-Transcript; $VerbosePreference = "SilentlyContinue"; exit }
    }
}

I am getting the error in Step 3. The secondary, inner, catch is getting the error at switch = "1". The function RetryAgain is being run and "A" is selected. This is confirmed because the output to the console is:

[END] Returning to the beginning.
False False True True True
True True True True True

The first five lines of code at the beginning of the while loop are:

while ($Step1Required -or $Step2Required -or $Step3Required -or $Step4Required -or $Step5Required) {
    Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
    if ($Step1Required) {
        # Step 1: Request general inputs
        Write-Host "[BEGIN] 1`: General inputs" -ForegroundColor Cyan

I created the second line to tell me what is happening and the output is: False False True True True

I can't, for the life of me, figure out how the last thing that happens before the continue is every $Step_Required is set to true. However, the first thing that happens at the start of the while loop is that $Step1Required and $Step2Required are set to false.

Please, if anyone can put me out of my misery, I would appreciate it.

Below is the whole script if anyone wants to view it.

# [Function] Input File Path
function Get-CSVFilePath {
    param (
        [Parameter(Mandatory = $true)]
        [string]$InputPath,
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo[]]$CSVFiles
    )
    try {
        if ($CSVFiles) {
            Write-Host "Select a CSV file to import:"
            $CSVFiles | Select-Object 'Name', 'LastWriteTime', 'Length', 'Mode' | Format-Table | Out-String | Write-Host
            $FileName = Read-Host -Prompt "Enter the CSV file name without extension (for most recent, press 'enter')"
            # Get the file of specified file name.
            if ($FileName) {
                $Success = $true
                $CSVOutput = Join-Path -Path $InputPath -ChildPath ($FileName + ".csv")
            }
            # Select the most recent file if FileName is blank
            else {
                $Success = $true
                $CSVOutput = Join-Path -Path $InputPath -ChildPath $CSVFiles[0].Name
            }
        }
        else {
            $Success = $false
            $CSVOutput = "[ERROR] No CSV files found in the input folder." | Write-Host -ForegroundColor Red
        }
    }
    catch {
        $Success = $false
        $CSVOutput = "[ERROR] $_" | Write-Host -ForegroundColor Red
    }
    # Debugging output:
    # Write-Host "Debug: `$Success is $Success"
    # Write-Host "Debug: `$CSVOutput is $CSVOutput"
    return @{
        "Success" = $Success
        "Output"  = $CSVOutput
    }
}

# [Function] Error - Retry or Start Again
function RetryAgain {
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Enter the step number to return to (as an integer).")]
        [int]$Step
    )
    $Choice = Read-Host "Press [R] to retry, [A] to start again, or any other key to exit"
    if ($Choice -eq 'R' -or $Choice -eq 'r') {
        continue
    }
    elseif ($Choice -eq 'A' -or $Choice -eq 'a') {
        Write-Host "[END] Returning to the beginning." -ForegroundColor Green
        Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
        $Step1Required = $true ; $Step2Required = $true ; $Step3Required = $true ; $Step4Required = $true ; $Step5Required = $true # Set-Variable -Name "Step${Step}Required" -Value $true
        Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
        continue
    }
    else { Stop-Transcript ; $VerbosePreference = "SilentlyContinue" ; exit }
}

# Main Script Body
Write-Host "Welcome to the Microsoft Teams Management Script" -ForegroundColor Green

# Set global variables
Write-Host "[BEGIN] 0`: Set global variables" -ForegroundColor Cyan
# Set path locations
$ParentPath = (Get-Location).Path
$FunctionsPath = Join-Path -Path $ParentPath -ChildPath "\1. Scripts\Functions\"
$1TRTAPath = Join-Path -Path $ParentPath -ChildPath "\1. Scripts\1. To Rule Them All\"
$LogsPath = Join-Path -Path $1TRTAPath -ChildPath "\0. Logs\"
$InputPath = Join-Path -Path $1TRTAPath -ChildPath "\1. Input\"

# Start Transcript
$VerbosePreference = "Continue" ; Start-Transcript -Path (Join-Path -Path $LogsPath -ChildPath "\$(Get-Date -Format 'yyyyMMdd_HHmm').log")

# Load functions scripts
. (Join-Path -Path $FunctionsPath -ChildPath "Connect-MicrosoftTeams.ps1") -ErrorAction Stop
. (Join-Path -Path $FunctionsPath -ChildPath "Connect-AzureAD.ps1") -ErrorAction Stop
. (Join-Path -Path $FunctionsPath -ChildPath "New-Team.ps1") -ErrorAction Stop
# . (Join-Path -Path $FunctionsPath -ChildPath "CreateTeam.ps1") -ErrorAction Stop
# . (Join-Path -Path $FunctionsPath -ChildPath "SetDynamicMembership.ps1") -ErrorAction Stop

# Run Connect functions
ConnectToTeams
ConnectToAzureAD

# Set all steps as required
$Step1Required = $true ; $Step2Required = $true ; $Step3Required = $true ; $Step4Required = $true ; $Step5Required = $true

# Task options as an Ordered hashtable with a dummy option at index 0
$TaskOptions = [Ordered]@{
    0 = ""  # Dummy option at index 0 (unused)
    1 = "Create a New Team"
    2 = "Get Team Info"
    3 = "Delete a Team"
    4 = "Set Team Archived State"
    5 = "Exit"
}
# Create prompt for task selection
$TaskOptionsPrompt = "Select a task:`n"
for ($i = 1; $i -lt $TaskOptions.Count; $i++) { $TaskOptionsPrompt += "[$($i)] $($TaskOptions[$i])`n" }
# Function names mapping hashtable
$TaskFunctions = [Ordered]@{
    1 = "Create-Team"
    2 = "Get-Team"
    3 = "Delete-Team"
    4 = "Set-TeamArchivedState"
    5 = "Exit"
}

$Step3Output = [Ordered]@{}

Write-Host "[END] 0`: Global variables set" -ForegroundColor Green

# Begin script steps
while ($Step1Required -or $Step2Required -or $Step3Required -or $Step4Required -or $Step5Required) {
    Write-Host "$Step1Required $Step2Required $Step3Required $Step4Required $Step5Required"
    if ($Step1Required) {
        # Step 1: Request general inputs
        Write-Host "[BEGIN] 1`: General inputs" -ForegroundColor Cyan
        $InputType = Read-Host "Choose input type: `n[1] Manual input `n[2] CSV input`n" ; If ( $InputType -notin 1..2) { RetryAgain }
        $ResponseType = Read-Host "Choose response type: `nEnter`n[1] Format-list`n[2] Format-table`n[3] CSV`n" ; If ( $InputType -notin 1..3) { RetryAgain }
        $TaskNumber = Read-Host $TaskOptionsPrompt ; If ( $InputType -notin 1..5) { RetryAgain }
        $TaskDescription = $TaskOptions[$TaskNumber - 6]
        $TaskFunction = $TaskFunctions[$TaskNumber - 1]
        if ($TaskNumber -eq "5") { Stop-Transcript ; $VerbosePreference = "SilentlyContinue" ; exit }
        Write-Host "You selected task $TaskNumber`: $TaskDescription"
        Write-Host "[INFO] You selected task $TaskDescription" -ForegroundColor Blue
        Write-Host "[END] 1`: General inputs set" -ForegroundColor Green
        $Step1Required = $false
    }

    if ($Step2Required) {
        # Step 2: Input CSV Path
        Write-Host "[BEGIN] 2`: CSV file path input" -ForegroundColor Cyan
        try {
            if ($InputType -eq 2) {
                $CSVFiles = Get-ChildItem -Path $InputPath -Filter "*.csv" | Sort-Object LastWriteTime -Descending
                $Step2Output = Get-CSVFilePath -InputPath $InputPath -CSVFiles $CSVFiles
                if ($Step2Output["Success"]) {
                    $CSVFilePath = $Step2Output["Output"]
                    $CSVData = Import-Csv -Path $CSVFilePath
                    Write-Host "[END] 2`: CSV file path inputted" -ForegroundColor Green
                    $Step2Required = $false
                }
                else { Write-Host $Step2Output["Output"] -ForegroundColor Red ; RetryAgain }
            }
            else { Write-Host "[END] 2`: CSV file path skipped" -ForegroundColor Green ; $Step2Required = $false }
        }
        catch { Write-Host "[ERROR] $_" -ForegroundColor Red ; RetryAgain }
    }

    if ($Step3Required) {
        # Specific functions group
        Write-Host "[BEGIN] 3`: Function started - $TaskNumber`: $TaskDescription" -ForegroundColor Cyan
        try {
            switch ($TaskNumber) {

                # New-Team script
                "1" {
                    # Check if the task number is 1 (Create a New Team)
                    try {
                        if ($InputType -eq 1) {
                            # Prompt the user for each parameter
                            $Name = Read-Host "[MANDATORY] Enter the team name"
                            $Description = Read-Host "[OPTIONAL] Enter the team description"
                            $MailNickName = Read-Host "[OPTIONAL] Enter the team mail nickname"
                            $Template = Read-Host "[OPTIONAL] Enter the team template (e.g., 'EDU_Class' or 'EDU_PLC')"
                            $OwnersInput = Read-Host "[MANDATORY] Enter the team owners (comma-separated)" ; $Owners = $OwnersInput -split ','
                            $MembersInput = Read-Host "[OPTIONAL] Enter the team members (comma-separated)" ; $Members = $MembersInput -split ','
                            # Call the New-Team function
                            $Step3Output[$Name] = NewTeam -Name $Name -Description $Description -MailNickName $MailNickName -Template $Template -Owners $Owners -Members $Members
                        }
                        # Check if the input type is 2 (CSV input)
                        elseif ($InputType -eq 2) {
                            # Prompt the user for CSV fields
                            $CSVFields = Read-Host "Enter the CSV fields (comma-separated). Options include: Name, Description, MailNickName, Template, Owners, and Members." -split ','
                            # Loop through each row in the CSV file
                            foreach ($Row in $CSVData) {
                                # Get the parameters from the CSV row
                                $Name = if ('Name' -in $CSVFields) { $Row.Name } else { Read-Host "[MANDATORY] Enter the team name:" }
                                $Description = if ('Description' -in $CSVFields) { $Row.Description } else { Read-Host "[OPTIONAL] Enter the team description:" }
                                $MailNickName = if ('MailNickName' -in $CSVFields) { $Row.MailNickName } else { Read-Host "[OPTIONAL] Enter the team mail nickname:" }
                                $Template = if ('Template' -in $CSVFields) { $Row.Template } else { Read-Host "[OPTIONAL] Enter the team template (e.g., 'EDU_Class' or 'EDU_PLC'):" }
                                $Owners = if ('Owners' -in $CSVFields) { @($Row.Owners -split ',') } else { Read-Host "[MANDATORY] Enter the team owners (comma-separated):" -split ',' }
                                $Members = if ('Members' -in $CSVFields) { @($Row.Members -split ',') } else { Read-Host "[OPTIONAL] Enter the team members (comma-separated):" -split ',' }
                                # Call the New-Team function
                                $Step3Output[$Name] = New-Team -Name $Name -Description $Description -MailNickName $MailNickName -Template $Template -Owners $Owners -Members $Members
                            }
                        }
                    }
                    catch { Write-Host "[ERROR] $_" -ForegroundColor Red ; RetryAgain }
                }

                "2" {  }
                "3" {  }
                "4" {  }
                "5" { Stop-Transcript; $VerbosePreference = "SilentlyContinue"; exit }
                Default { Write-Host "[WARNING] Invalid choice. Please try again." -ForegroundColor Yellow ; RetryAgain }
            }
            # For all functions, check for success.
            $FirstItem = $Step3Output.GetEnumerator() | Select-Object -First 1
            if ($FirstItem) {
                # Check if the value of the first item is a hashtable
                if ($FirstItem.Value -is [Hashtable]) {
                    # Check if "Success" is $true in the nested hashtable
                    if ($FirstItem.Value["Success"]) {
                        Write-Host "[END] 3`: Function completed - $TaskNumber`: $TaskDescription" -ForegroundColor Green
                        $Step3Required = $false
                    }
                    else { Write-Host $FirstItem.Value["Output"] -ForegroundColor Red ; RetryAgain }
                }
                else {
                    # Check if "Success" is $true in the simple hashtable
                    if ($Step3Output["Success"]) {
                        Write-Host "[END] 3`: Function completed - $TaskNumber`: $TaskDescription" -ForegroundColor Green
                        $Step3Required = $false
                    }
                    else { Write-Host $Step3Output["Output"] -ForegroundColor Red ; RetryAgain }
                }
            }
            else { Write-Host "[ERROR] Step3Output is an empty hashtable" -ForegroundColor Red ; RetryAgain }
        }
        catch { Write-Host "[ERROR] $_" -ForegroundColor Red ; RetryAgain }
    }

    if ($Step4Required) {
        # Step 4: Display or save the $Step3Output
        Write-Host "[BEGIN] 4`: Displaying and Saving Outputs" -ForegroundColor Cyan
        switch ($ResponseType) {
            "1" { $Step3Output | Format-List }
            "2" { $Step3Output | Format-Table }
            "3" {
                # Construct the output path
                $CSVOutputPath = Join-Path -Path $1TRTAPath -ChildPath ("\2. " + $TaskFunction + "\$(Get-Date -Format 'yyyyMMdd_HHmm').csv")
                # Check if the directory exists, if not, create it
                $CSVOutputDirectory = Split-Path -Path $CSVOutputPath -Parent
                if (!(Test-Path -Path $CSVOutputDirectory)) { New-Item -ItemType Directory -Force -Path $CSVOutputDirectory }
                # Export to CSV
                $Step3Output | Export-Csv -Path $CSVOutputPath -NoTypeInformation
            }
            default { Write-Host "[ERROR] Invalid ResponseType. Please select either Format-List, Format-Table, or CSV." -ForegroundColor Red }
        }
        Write-Host "[END] 4`: Outputs displayed and saved" -ForegroundColor Green
        $Step4Required = $false
    }

    if ($Step5Required) {
        Write-Host "You have reached the end of the script."
        $Choice = Read-Host "Press [A] to start again, or any other key to exit"
        if ($Choice -eq 'A' -or $Choice -eq 'a') {
            Write-Host "[END] Returning to the beginning." -ForegroundColor Green
            $Step1Required = $true # Set-Variable -Name "Step${Step}Required" -Value $true
            continue
        }
        else { Stop-Transcript; $VerbosePreference = "SilentlyContinue"; exit }
    }
}

Stop-Transcript
$VerbosePreference = "SilentlyContinue"

4
  • 2
    Your problem is basically the same as this one: stackoverflow.com/questions/6766722/…. RetryAgain runs in a child scope - it can read the parent $Step1Required variable but when you assign a value it creates a new variable inside the RetryAgain function's scope, and the changes are lost when the RetryAgain exits. See learn.microsoft.com/en-us/powershell/module/… for more details. Commented Sep 28, 2023 at 11:16
  • 1
    Inside function RetryAgain, assign the variable as follows: $Script:Step1Required = $true Commented Sep 28, 2023 at 12:03
  • 1
    Arrays and hash tables are reference variables. That mean you can use them from within a function and modify their content. This means that code like this works:$StepsRequired = @($true, $true, $true, $true, $true, $true);function RetryAgain {$StepsRequired[2] = $false;$StepsRequired[5] = $false;};RetryAgain;$StepsRequired Commented Sep 28, 2023 at 14:31
  • Thank you mclayton and David Trevor. Now that you have answered my question, what do we do with this page (that is, do we mark it answered/complete)? Commented Sep 28, 2023 at 14:54

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.