I used ChatGPT to generate the code to understand how to do it myself, and it mostly works. The script is to check my TV & Movie folders for missing files to make Plex work better (folder.jpg, theme.mp3 and so on) and show which are missing, and how many.
It works perfectly fine on the movies folder, but I can't (nor can the AI) work out how to fix it to click the TV or TV ended button and make it search those folders.
My best guess is the $pathMap variable on line 167 is the key and it's not updating correctly for this part - $global:fixedPath = $pathMap
Would anyone be able to correct it and explain how I could work out how to do it?
Windows 10 22H2 and Powershell PSVersion 5.1.19041.5737
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Create form
$form = New-Object System.Windows.Forms.Form
$form.Text = "Marion's Plex checker"
$form.Size = New-Object System.Drawing.Size(700, 700)
$form.StartPosition = "CenterScreen"
# Fixed folder path
$fixedPath = "D:\ServerFolders\Videos\02 - Movies"
# Path label
$label = New-Object System.Windows.Forms.Label
$label.Text = "Checking subfolders in: $fixedPath"
$label.Location = New-Object System.Drawing.Point(120, 20)
$label.Size = New-Object System.Drawing.Size(550, 20)
$form.Controls.Add($label)
# Summary label
$summaryLabel = New-Object System.Windows.Forms.Label
$summaryLabel.Text = ""
$summaryLabel.Location = New-Object System.Drawing.Point(120, 45)
$summaryLabel.Size = New-Object System.Drawing.Size(550, 20)
$form.Controls.Add($summaryLabel)
# Output box
$logBox = New-Object System.Windows.Forms.TextBox
$logBox.Multiline = $true
$logBox.ScrollBars = "Vertical"
$logBox.Location = New-Object System.Drawing.Point(120, 75)
$logBox.Size = New-Object System.Drawing.Size(550, 350)
$logBox.Anchor = "Top,Bottom,Left,Right"
$logBox.ReadOnly = $true
$form.Controls.Add($logBox)
# Search file buttons (on the left)
$fileTypes = @("theme.mp3", "folder.jpg", "background.jpg", "poster.jpg")
$fileButtons = @{}
$yPos = 20
$global:selectedFile = "theme.mp3"
foreach ($file in $fileTypes) {
$btn = New-Object System.Windows.Forms.Button
$btn.Text = $file
$btn.Tag = $file
$btn.Location = New-Object System.Drawing.Point(10, $yPos)
$btn.Size = New-Object System.Drawing.Size(100, 30)
$form.Controls.Add($btn)
$fileButtons[$file] = $btn
$yPos += 40
$btn.Add_Click({
$global:selectedFile = $this.Tag
# Reset styles
foreach ($b in $fileButtons.Values) {
$b.UseVisualStyleBackColor = $true
}
# Highlight selected
# Reset styles
foreach ($b in $fileButtons.Values) {
$b.UseVisualStyleBackColor = $true
$b.BackColor = [System.Drawing.SystemColors]::Control
}
# Highlight selected
$this.UseVisualStyleBackColor = $false
$this.BackColor = 'LightBlue'
$summaryLabel.Text = "Selected file: $global:selectedFile"
})
}
# Scan button
$scanButton = New-Object System.Windows.Forms.Button
$scanButton.Text = "Scan"
$scanButton.Location = New-Object System.Drawing.Point(10, 440)
$scanButton.Size = New-Object System.Drawing.Size(100, 30)
$form.Controls.Add($scanButton)
# Create a new button for Movies, positioned to the right of the Scan button
$moviesButton = New-Object System.Windows.Forms.Button
$moviesButton.Text = "Movies"
$moviesButton.Size = New-Object System.Drawing.Size(100, 30)
$moviesButton.Location = New-Object System.Drawing.Point(120, 440)
# Create a new button for TV, positioned next to the Movies button
$tvButton = New-Object System.Windows.Forms.Button
$tvButton.Text = "TV"
$tvButton.Size = New-Object System.Drawing.Size(100, 30)
$tvButton.Location = New-Object System.Drawing.Point(230, 440)
# Create a new button for TV Ended, positioned next to the TV button
$tvEndedButton = New-Object System.Windows.Forms.Button
$tvEndedButton.Text = "TV Ended"
$tvEndedButton.Size = New-Object System.Drawing.Size(100, 30)
$tvEndedButton.Location = New-Object System.Drawing.Point(340, 440) # Right of the TV button
$tvEndedButton.Add_Click({
Start-Process "D:\ServerFolders\Videos\TV (Ended)" # Link to the TV Ended path
})
$form.Controls.Add($tvEndedButton)
# Path variables for Movies, TV, and TV Ended
$pathMap = @{
"Movies" = "D:\ServerFolders\Videos\02 - Movies"
"TV Current" = "D:\ServerFolders\Videos\01 - TV"
"TV Ended" = "D:\ServerFolders\Videos\TV (Ended)"
}
# Default path for Movies
$global:fixedPath = $pathMap["Movies"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Add click event for Movies button
$moviesButton.Add_Click({
$global:fixedPath = $pathMap["Movies"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$moviesButton.BackColor = 'LightGreen'
$tvButton.BackColor = [System.Drawing.SystemColors]::Control
$tvEndedButton.BackColor = [System.Drawing.SystemColors]::Control
# Start scanning the Movies path
Write-Host "Scanning Movies path: $global:fixedPath"
Scan-Folder -path $global:fixedPath
})
# Add click event for TV button
$tvButton.Add_Click({
$global:fixedPath = $pathMap["TV Current"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$tvButton.BackColor = 'LightGreen'
$moviesButton.BackColor = [System.Drawing.SystemColors]::Control
$tvEndedButton.BackColor = [System.Drawing.SystemColors]::Control
# Start scanning the TV Current path
Write-Host "Scanning TV path: $global:fixedPath"
Scan-Folder -path $global:fixedPath
})
# Add click event for TV Ended button
$tvEndedButton.Add_Click({
$global:fixedPath = $pathMap["TV Ended"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$tvEndedButton.BackColor = 'LightGreen'
$moviesButton.BackColor = [System.Drawing.SystemColors]::Control
$tvButton.BackColor = [System.Drawing.SystemColors]::Control
# Start scanning the TV Ended path
Write-Host "Scanning TV Ended path: $global:fixedPath"
Scan-Folder -path $global:fixedPath
})
# Function to scan the folder
function Scan-Folder {
param(
[string]$path
)
Write-Host "Scanning folder: $path"
# Insert the actual folder scanning logic here
}
# Path variables for Movies, TV, and TV Ended
$pathMap = @{
"Movies" = "D:\ServerFolders\Videos\02 - Movies"
"TV Current" = "D:\ServerFolders\Videos\01 - TV"
"TV Ended" = "D:\ServerFolders\Videos\TV (Ended)"
}
# Default path for Movies
$global:fixedPath = $pathMap["Movies"]
# Add click event for Movies button
$moviesButton.Add_Click({
$global:fixedPath = $pathMap["Movies"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$moviesButton.BackColor = 'LightGreen'
$tvButton.BackColor = [System.Drawing.SystemColors]::Control
$tvEndedButton.BackColor = [System.Drawing.SystemColors]::Control
# Trigger the scanning logic
Scan-Folder -path $global:fixedPath
})
# Add click event for TV button
$tvButton.Add_Click({
$global:fixedPath = $pathMap["TV Current"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$tvButton.BackColor = 'LightGreen'
$moviesButton.BackColor = [System.Drawing.SystemColors]::Control
$tvEndedButton.BackColor = [System.Drawing.SystemColors]::Control
# Trigger the scanning logic
Scan-Folder -path $global:fixedPath
})
# Add click event for TV Ended button
$tvEndedButton.Add_Click({
$global:fixedPath = $pathMap["TV Ended"]
$pathLabel.Text = "Scanning path: $global:fixedPath"
# Change button colors to indicate active path
$tvEndedButton.BackColor = 'LightGreen'
$moviesButton.BackColor = [System.Drawing.SystemColors]::Control
$tvButton.BackColor = [System.Drawing.SystemColors]::Control
# Trigger the scanning logic
Scan-Folder -path $global:fixedPath
})
# Function to scan the folder
function Scan-Folder {
param(
[string]$path
)
Write-Host "Scanning folder: $path"
# Insert the actual scanning logic here to process the folder
}
# Right of the Movies button
$tvButton.Add_Click({
Start-Process "D:\ServerFolders\Videos\01 - TV" # Link to the TV path
})
$form.Controls.Add($tvButton)
# Right of the Scan button
$moviesButton.Add_Click({
Start-Process "D:\ServerFolders\Videos\02 - Movies" # Link to the Movies path
})
$form.Controls.Add($moviesButton)
# Globals
$global:missingItems = @()
function Run-Scan {
$logBox.Clear()
$summaryLabel.Text = ""
$log = @()
$logPath = Join-Path $fixedPath "missing_${global:selectedFile}_log.txt"
$csvPath = Join-Path $fixedPath "missing_${global:selectedFile}_report.csv"
$global:missingItems = @()
if (-not (Test-Path $fixedPath)) {
[System.Windows.Forms.MessageBox]::Show("Invalid folder path.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
$folders = Get-ChildItem -Path $fixedPath -Directory
foreach ($folder in $folders) {
$targetFile = Join-Path $folder.FullName $global:selectedFile
if (-not (Test-Path -LiteralPath $targetFile)) {
$global:missingItems += $folder
$logLine = "$($folder.Name)"
$logBox.AppendText($logLine + "`r`n")
$log += $logLine
}
}
if ($global:missingItems.Count -eq 0) {
$summaryLabel.Text = "All folders have $($global:selectedFile)."
} else {
$summaryLabel.Text = "Missing $($global:selectedFile) from [$($global:missingItems.Count)] folders."
}
$log | Out-File -FilePath $logPath -Encoding UTF8
$logBox.AppendText("`r`nLog saved to: $logPath`r`n")
$exportButton.Enabled = $global:missingItems.Count -gt 0
}
$scanButton.Add_Click({ Run-Scan })
# Export to CSV button
$exportButton = New-Object System.Windows.Forms.Button
$exportButton.Text = "Export to CSV"
$exportButton.Location = New-Object System.Drawing.Point(10, 400)
$exportButton.Size = New-Object System.Drawing.Size(100, 30)
$exportButton.Enabled = $false
$form.Controls.Add($exportButton)
$exportButton.Add_Click({
$csvPath = Join-Path $fixedPath "missing_$($global:selectedFile)_report.csv"
$csvPath = Join-Path $fixedPath "missing_$($global:selectedFile)_report.csv"
$headerLines = @(
"File type: $($global:selectedFile)",
"Total missing: $($global:missingItems.Count)`r`n"
)
$headerLines | Out-File -FilePath $csvPath -Encoding UTF8
$global:missingItems | ForEach-Object {
[PSCustomObject]@{ FolderName = $_.Name; Path = $_.FullName }
} | Export-Csv -Path $csvPath -Append -NoTypeInformation -Encoding UTF8
$logBox.AppendText("CSV exported to: $csvPath`r`n")
})
$form.Topmost = $true
$form.Add_Shown({ $form.Activate() })
[void]$form.ShowDialog()
Your problem is one of variable scoping:
You use both $fixedPath
and $global:fixedPath
to refer to what you think are the same variable, but they're not:
PowerShell scripts run in a child scope of the caller by default, meaning that the script's scope (which can have child scopes of its own, notably inside functions) is not the same as the global scope ($global:
); you can use the $script:
scope specifier to refer to the script's top-level scope (even from embedded functions).
While you can read variables defined in an ancestral scope through referencing them by name only (e.g. $fixedPath
), when you assign a value to them without a scope specifier, you implicitly create a new variable by that name in the current scope, which then shadows (eclipses) the ancestral copy - unless you explicitly refer to it via a scope specifier.
$global:fixedPath
and $fixedPath
in your code, the latter, script-scope copy shadows the global copy if you try to read it later with just $fixedPath
.$global:fixedPath
won't be seen if you use just $fixedPath
, which is what constitutes your problem.For robustness and conceptual clarity I suggest always using a scope specifier to refer to variables designed for cross-scope use; however, do not use $global:
, as global variables are invariably session-global, and therefore linger after your script exits, polluting the global namespace.
Instead, use the $script:
scope:
That is, replace all occurrences of $global:fixedPath
and $fixedPath
with $script:fixedPath