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"
RetryAgainruns in a child scope - it can read the parent$Step1Requiredvariable but when you assign a value it creates a new variable inside theRetryAgainfunction's scope, and the changes are lost when theRetryAgainexits. See learn.microsoft.com/en-us/powershell/module/… for more details.$StepsRequired = @($true, $true, $true, $true, $true, $true);function RetryAgain {$StepsRequired[2] = $false;$StepsRequired[5] = $false;};RetryAgain;$StepsRequired