Conversation
## Summary - Adds PowerShell script to automate Dell NVMe 7450 firmware updates - Follows organization script template (RMM Variable Declaration, Input Handling, Script Logic) - Supports both interactive and RMM execution modes ## Features - Dell system verification via WMI - BOSS-N1 controller and 7450 drive detection via OMSA - SHA256 hash verification for firmware downloads - Configurable restart behavior (-AutoRestart, -NoRestart) - Installer timeout protection (default 600s) - Comprehensive transcript and file logging
This script monitors the C:\vixtemp folder size and archives the oldest files when the threshold is exceeded, maintaining 5 rotating archive versions for recovery. It includes logging, error handling, and checks for sufficient disk space.
📝 WalkthroughWalkthroughAdds two comprehensive PowerShell scripts: one automating Dell NVMe 7450 firmware detection and updates via OMSA and Backblaze repository integration, the other monitoring and archiving temporary files with archive rotation and RMM support. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Script as Update-Dell7450Firmware
participant OMSA as OpenManage OMSA
participant Drives as NVMe 7450 Drives
participant Repo as Backblaze Repository
participant Installer as Firmware Installer
User->>Script: Execute script
Script->>Script: Initialize-Environment
Script->>OMSA: Test-OMSA (validate installation)
OMSA-->>Script: OMSA available
Script->>OMSA: Get-BossController (locate controller)
OMSA-->>Script: BOSS-N1 controller
Script->>OMSA: Get-7450DriveInfo (enumerate drives)
OMSA-->>Script: Drive details (variant, firmware)
Script->>Script: Compare-FirmwareVersion (current vs 1.4.0)
rect rgba(100, 150, 200, 0.5)
Note over Script: Firmware update needed?
end
Script->>Repo: Get-FirmwareFromRepo (download per variant)
Repo-->>Script: Firmware file
Script->>Installer: Install-Firmware (silent install)
Installer-->>Script: Success
Script->>Script: Prompt for system restart
User->>Script: Confirm restart
Script->>Drives: Initiate system reboot
sequenceDiagram
participant Scheduler as RMM/Scheduler
participant Script as VixTempCleanup
participant FileSystem as File System
participant Archive as Compress-Archive
participant EventLog as Windows Event Log
participant RMM as RMM System
Scheduler->>Script: Execute script
Script->>Script: Initialize-Directories & locks
Script->>FileSystem: Test SourcePath (C:\vixtemp)
FileSystem-->>Script: Exists
Script->>Script: Test-LockFile & prevent concurrent runs
Script->>FileSystem: Get-FolderSizeMB
FileSystem-->>Script: Current size
rect rgba(100, 150, 200, 0.5)
Note over Script: Size exceeds threshold?
end
Script->>FileSystem: Get-FilesToArchive (oldest files)
FileSystem-->>Script: Selected files
Script->>Archive: New-VixTempArchive (compress)
Archive-->>Script: Archive metadata
Script->>FileSystem: Remove-ArchivedFiles
FileSystem-->>Script: Files deleted
Script->>FileSystem: Remove-OldArchives (rotate, max 5)
FileSystem-->>Script: Old archives cleaned
Script->>EventLog: Write-NinjaAlert
EventLog-->>RMM: Alert logged
Script->>Script: Update-LastSuccess & cleanup
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@app-vixwin/VixTempCleanup.ps1`:
- Line 586: The log message uses $spaceCheck.FreeMB but labels it as "GB" —
update the Write-Log call in VixTempCleanup.ps1 to either convert FreeMB and
RequiredMB to gigabytes (divide values by 1024 and format to desired decimal
places) or change the label to "MB" to match the units; locate the Write-Log
invocation that references $spaceCheck.FreeMB and $spaceCheck.RequiredMB and
make the unit/value change consistently so the log accurately reflects the
measured units.
In `@Update-Dell7450Firmware.ps1`:
- Around line 443-448: The script starts a transcript with Start-Transcript but
never calls Stop-Transcript; ensure you stop the transcript when the script
finishes by adding a Stop-Transcript call tied to the script lifecycle—either
add a Stop-Transcript in the Main function's cleanup/finally block or invoke
Stop-Transcript immediately after the Main call, and guard it so it only runs if
Start-Transcript was used (e.g., check a $TranscriptStarted flag or use
Try/Finally around Start-Transcript/Stop-Transcript) to guarantee proper logging
closure.
- Around line 251-283: Get-FirmwareFromRepo assumes $FirmwareFiles[$Variant] is
a string but $FirmwareFiles stores a nested hashtable with keys like FileName
and SHA256; update the function to read the file name from the nested entry
(e.g. use the FileName property from $FirmwareFiles[$Variant]) when building
$downloadUrl and $localPath, and optionally extract the SHA256 value for later
integrity checks; adjust references to $FirmwareFiles[$Variant] throughout
Get-FirmwareFromRepo so they use the .FileName field instead of treating the
entry as a string.
- Around line 78-99: The snippet contains orphaned/duplicate definitions of
$FirmwareFiles (partial hashtable), $TempPath and $LogFile after
Start-Transcript which will cause parse errors and shadow earlier correct
definitions; remove the stray lines that redefine "RI"/"MU" and the subsequent
$TempPath/$LogFile assignments so the script uses the original $FirmwareFiles,
$TempPath and $LogFile defined before Start-Transcript and avoids the broken
hashtable fragment and duplicate variable reassignments.
- Around line 421-434: The restart block unconditionally sleeps and calls
Restart-Computer, ignoring the $AutoRestart and $NoRestart switches; modify the
logic around Start-Sleep and Restart-Computer in the same conditional that logs
"Initiating system restart" so it first checks $NoRestart (skip restart and
log/exit appropriately) and then respects $AutoRestart (if true, perform the
sleep+Restart-Computer sequence; if false, prompt the user and do not
auto-restart), updating messages via Write-Host/Write-Log accordingly and
preserving the existing "Installation failed" branch behavior.
🧹 Nitpick comments (5)
Update-Dell7450Firmware.ps1 (2)
18-41: Unused$InstallerTimeoutSecondsparameter and SHA256 hashes.The
$InstallerTimeoutSecondsparameter (Line 15) is never used inInstall-Firmware. The SHA256 hashes in$FirmwareFilesare defined but never validated after download. Consider implementing hash verification or removing the placeholders.
285-321: Silent installation looks correct, but timeout parameter is unused.The
Install-Firmwarefunction handles exit codes appropriately. However, the$InstallerTimeoutSecondsparameter defined at line 15 is never passed toStart-Process. The-Waitflag will wait indefinitely.Proposed fix to use timeout
function Install-Firmware { - param([string]$InstallerPath) + param( + [string]$InstallerPath, + [int]$TimeoutSeconds = 600 + ) # ... try { - $process = Start-Process -FilePath $InstallerPath -ArgumentList "/s", "/f" -Wait -PassThru -NoNewWindow + $process = Start-Process -FilePath $InstallerPath -ArgumentList "/s", "/f" -PassThru -NoNewWindow + $completed = $process.WaitForExit($TimeoutSeconds * 1000) + if (-not $completed) { + Write-Log "Installer timed out after $TimeoutSeconds seconds" "ERROR" + $process.Kill() + return $false + }app-vixwin/VixTempCleanup.ps1 (3)
108-124: Log level is double-prefixed in message and parameter.Some callers include
[INFO],[WARN],[ERROR]in the$Messageparameter while also passing-Level. This results in duplicate prefixes like[WARN] [WARN] Stale lock file...at line 150. Consider either removing the prefix from messages or adjustingWrite-Logto handle this.Proposed fix - adjust callers to not include prefix
For example, at line 150:
- Write-Log "[WARN] Stale lock file detected (age: $([math]::Round($lockAge.TotalMinutes, 1)) minutes). Removing." -Level WARN + Write-Log "Stale lock file detected (age: $([math]::Round($lockAge.TotalMinutes, 1)) minutes). Removing." -Level WARNOr update the function to strip existing prefixes:
function Write-Log { param( [string]$Message, [ValidateSet('INFO','WARN','ERROR')] [string]$Level = 'INFO' ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + # Strip existing level prefix if present + $Message = $Message -replace '^\[(INFO|WARN|ERROR)\]\s*', '' $logEntry = "[$timestamp] [$Level] $Message"
264-275: File accessibility check has a subtle issue.The pattern
[System.IO.File]::OpenRead($file.FullName).Close()doesn't dispose the stream if an exception occurs betweenOpenReadandClose(). Use atry/finallyor the-disposepattern.Proposed fix
try { # Test if file is accessible - $null = [System.IO.File]::OpenRead($file.FullName).Close() + $stream = [System.IO.File]::OpenRead($file.FullName) + $stream.Dispose() $filePaths += $file.FullName $archivedFiles += $file $originalSizeBytes += $file.Length }
337-339: Empty directory cleanup may miss nested directories.The empty directory cleanup iterates once, but nested empty directories may require multiple passes. Consider using
-Recursewith depth-first sorting or a loop until no empty directories remain.Proposed fix for depth-first cleanup
# Clean up empty subdirectories (depth-first to handle nested empties) - Get-ChildItem $Config.SourcePath -Directory -Recurse -ErrorAction SilentlyContinue | - Where-Object { (Get-ChildItem $_.FullName -Recurse -File -ErrorAction SilentlyContinue).Count -eq 0 } | - Remove-Item -Force -ErrorAction SilentlyContinue + do { + $emptyDirs = Get-ChildItem $Config.SourcePath -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { (Get-ChildItem $_.FullName -File -ErrorAction SilentlyContinue).Count -eq 0 -and + (Get-ChildItem $_.FullName -Directory -ErrorAction SilentlyContinue).Count -eq 0 } + $emptyDirs | Remove-Item -Force -ErrorAction SilentlyContinue + } while ($null -ne $emptyDirs -and $emptyDirs.Count -gt 0)
| Stop-Transcript | ||
| exit 2 | ||
| } | ||
| Write-Log "[INFO] Disk space check: $($spaceCheck.FreeMB) GB free, $($spaceCheck.RequiredMB) MB required. OK." |
There was a problem hiding this comment.
Unit mismatch in log message: says "GB" but value is in MB.
The log message states $($spaceCheck.FreeMB) GB free but FreeMB is in megabytes, not gigabytes.
Proposed fix
- Write-Log "[INFO] Disk space check: $($spaceCheck.FreeMB) GB free, $($spaceCheck.RequiredMB) MB required. OK."
+ Write-Log "[INFO] Disk space check: $($spaceCheck.FreeMB) MB free, $($spaceCheck.RequiredMB) MB required. OK."📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Write-Log "[INFO] Disk space check: $($spaceCheck.FreeMB) GB free, $($spaceCheck.RequiredMB) MB required. OK." | |
| Write-Log "[INFO] Disk space check: $($spaceCheck.FreeMB) MB free, $($spaceCheck.RequiredMB) MB required. OK." |
🤖 Prompt for AI Agents
In `@app-vixwin/VixTempCleanup.ps1` at line 586, The log message uses
$spaceCheck.FreeMB but labels it as "GB" — update the Write-Log call in
VixTempCleanup.ps1 to either convert FreeMB and RequiredMB to gigabytes (divide
values by 1024 and format to desired decimal places) or change the label to "MB"
to match the units; locate the Write-Log invocation that references
$spaceCheck.FreeMB and $spaceCheck.RequiredMB and make the unit/value change
consistently so the log accurately reflects the measured units.
| #region Script Logic | ||
| # Start transcript for comprehensive logging | ||
| if (-not (Test-Path $TempPath)) { | ||
| New-Item -ItemType Directory -Path $TempPath -Force | Out-Null | ||
| } | ||
| $TranscriptPath = "$TempPath\firmware_update_transcript.log" | ||
| Start-Transcript -Path $TranscriptPath -Append -ErrorAction SilentlyContinue | ||
|
|
||
| # Output diagnostic information | ||
| Write-Host "========================================" | ||
| Write-Host "Dell NVMe 7450 Firmware Update Script" | ||
| Write-Host "========================================" | ||
| Write-Host "Description: $Description" | ||
| Write-Host "Log Path: $LogFile" | ||
| Write-Host "RMM Mode: $(if ($RMM -eq 1) { 'Yes' } else { 'No' })" | ||
| Write-Host "========================================" | ||
|
|
||
| "RI" = "Express-Flash-PCIe-SSD_Firmware_JHKXR_WN64_1.4.0_A03_01.EXE" | ||
| "MU" = "Express-Flash-PCIe-SSD_Firmware_JHKXR_WN64_1.4.0_A03_01.EXE" # Same installer for RI/MU | ||
| } | ||
| $TempPath = "$env:TEMP\Dell7450Firmware" | ||
| $LogFile = "$TempPath\firmware_update.log" |
There was a problem hiding this comment.
Critical: Orphaned/duplicate code corrupts variable definitions.
Lines 95-99 appear to be orphaned code that redefines $FirmwareFiles, $TempPath, and $LogFile after the transcript has started. This code is syntactically broken (starts mid-hashtable without context) and will cause parse errors or unexpected behavior. The $FirmwareFiles redefinition here is a simple string mapping, conflicting with the nested hashtable at lines 30-39.
Remove lines 95-99 entirely—they appear to be leftover from a merge or refactor.
Proposed fix
Write-Host "========================================"
- "RI" = "Express-Flash-PCIe-SSD_Firmware_JHKXR_WN64_1.4.0_A03_01.EXE"
- "MU" = "Express-Flash-PCIe-SSD_Firmware_JHKXR_WN64_1.4.0_A03_01.EXE" # Same installer for RI/MU
-}
-$TempPath = "$env:TEMP\Dell7450Firmware"
-$LogFile = "$TempPath\firmware_update.log"
-
# Initialize
function Write-Log {🤖 Prompt for AI Agents
In `@Update-Dell7450Firmware.ps1` around lines 78 - 99, The snippet contains
orphaned/duplicate definitions of $FirmwareFiles (partial hashtable), $TempPath
and $LogFile after Start-Transcript which will cause parse errors and shadow
earlier correct definitions; remove the stray lines that redefine "RI"/"MU" and
the subsequent $TempPath/$LogFile assignments so the script uses the original
$FirmwareFiles, $TempPath and $LogFile defined before Start-Transcript and
avoids the broken hashtable fragment and duplicate variable reassignments.
| # Download firmware from Backblaze | ||
| function Get-FirmwareFromRepo { | ||
| param([string]$Variant) | ||
|
|
||
| if (-not $FirmwareFiles.ContainsKey($Variant)) { | ||
| Write-Log "No firmware mapping for variant: $Variant" "ERROR" | ||
| return $null | ||
| } | ||
|
|
||
| $fileName = $FirmwareFiles[$Variant] | ||
| $downloadUrl = "$BackblazeBaseUrl/$fileName" | ||
| $localPath = "$TempPath\$fileName" | ||
|
|
||
| Write-Log "Downloading firmware from: $downloadUrl" | ||
|
|
||
| try { | ||
| $ProgressPreference = 'SilentlyContinue' # Speed up download | ||
| Invoke-WebRequest -Uri $downloadUrl -OutFile $localPath -UseBasicParsing -ErrorAction Stop | ||
|
|
||
| if (Test-Path $localPath) { | ||
| $fileSize = (Get-Item $localPath).Length / 1MB | ||
| Write-Log "Download complete: $fileName ($([math]::Round($fileSize, 2)) MB)" | ||
| return $localPath | ||
| } else { | ||
| Write-Log "Download failed: File not found after download" "ERROR" | ||
| return $null | ||
| } | ||
| } | ||
| catch { | ||
| Write-Log "Download failed: $_" "ERROR" | ||
| return $null | ||
| } | ||
| } |
There was a problem hiding this comment.
Firmware download uses wrong data structure.
Get-FirmwareFromRepo accesses $FirmwareFiles[$Variant] expecting a string (Line 260), but the actual $FirmwareFiles definition at lines 30-39 is a nested hashtable with FileName and SHA256 keys. This will fail at runtime.
Proposed fix
function Get-FirmwareFromRepo {
param([string]$Variant)
if (-not $FirmwareFiles.ContainsKey($Variant)) {
Write-Log "No firmware mapping for variant: $Variant" "ERROR"
return $null
}
- $fileName = $FirmwareFiles[$Variant]
+ $firmwareInfo = $FirmwareFiles[$Variant]
+ $fileName = $firmwareInfo.FileName
$downloadUrl = "$BackblazeBaseUrl/$fileName"
$localPath = "$TempPath\$fileName"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Download firmware from Backblaze | |
| function Get-FirmwareFromRepo { | |
| param([string]$Variant) | |
| if (-not $FirmwareFiles.ContainsKey($Variant)) { | |
| Write-Log "No firmware mapping for variant: $Variant" "ERROR" | |
| return $null | |
| } | |
| $fileName = $FirmwareFiles[$Variant] | |
| $downloadUrl = "$BackblazeBaseUrl/$fileName" | |
| $localPath = "$TempPath\$fileName" | |
| Write-Log "Downloading firmware from: $downloadUrl" | |
| try { | |
| $ProgressPreference = 'SilentlyContinue' # Speed up download | |
| Invoke-WebRequest -Uri $downloadUrl -OutFile $localPath -UseBasicParsing -ErrorAction Stop | |
| if (Test-Path $localPath) { | |
| $fileSize = (Get-Item $localPath).Length / 1MB | |
| Write-Log "Download complete: $fileName ($([math]::Round($fileSize, 2)) MB)" | |
| return $localPath | |
| } else { | |
| Write-Log "Download failed: File not found after download" "ERROR" | |
| return $null | |
| } | |
| } | |
| catch { | |
| Write-Log "Download failed: $_" "ERROR" | |
| return $null | |
| } | |
| } | |
| # Download firmware from Backblaze | |
| function Get-FirmwareFromRepo { | |
| param([string]$Variant) | |
| if (-not $FirmwareFiles.ContainsKey($Variant)) { | |
| Write-Log "No firmware mapping for variant: $Variant" "ERROR" | |
| return $null | |
| } | |
| $firmwareInfo = $FirmwareFiles[$Variant] | |
| $fileName = $firmwareInfo.FileName | |
| $downloadUrl = "$BackblazeBaseUrl/$fileName" | |
| $localPath = "$TempPath\$fileName" | |
| Write-Log "Downloading firmware from: $downloadUrl" | |
| try { | |
| $ProgressPreference = 'SilentlyContinue' # Speed up download | |
| Invoke-WebRequest -Uri $downloadUrl -OutFile $localPath -UseBasicParsing -ErrorAction Stop | |
| if (Test-Path $localPath) { | |
| $fileSize = (Get-Item $localPath).Length / 1MB | |
| Write-Log "Download complete: $fileName ($([math]::Round($fileSize, 2)) MB)" | |
| return $localPath | |
| } else { | |
| Write-Log "Download failed: File not found after download" "ERROR" | |
| return $null | |
| } | |
| } | |
| catch { | |
| Write-Log "Download failed: $_" "ERROR" | |
| return $null | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@Update-Dell7450Firmware.ps1` around lines 251 - 283, Get-FirmwareFromRepo
assumes $FirmwareFiles[$Variant] is a string but $FirmwareFiles stores a nested
hashtable with keys like FileName and SHA256; update the function to read the
file name from the nested entry (e.g. use the FileName property from
$FirmwareFiles[$Variant]) when building $downloadUrl and $localPath, and
optionally extract the SHA256 value for later integrity checks; adjust
references to $FirmwareFiles[$Variant] throughout Get-FirmwareFromRepo so they
use the .FileName field instead of treating the entry as a string.
|
|
||
| # Prompt for restart | ||
| Write-Host "`nThe system will restart in 30 seconds..." -ForegroundColor Yellow | ||
| Write-Host "Press Ctrl+C to cancel restart" -ForegroundColor Yellow | ||
|
|
||
| Start-Sleep -Seconds 30 | ||
|
|
||
| Write-Log "Initiating system restart" | ||
| Restart-Computer -Force | ||
| } else { | ||
| Write-Host "`n*** Installation failed - check log for details ***" -ForegroundColor Red | ||
| Write-Log "Exiting: Firmware installation failed" "ERROR" | ||
| exit 1 | ||
| } |
There was a problem hiding this comment.
Restart logic ignores $AutoRestart and $NoRestart parameters.
The restart behavior hardcoded at lines 423-429 always waits 30 seconds then restarts, ignoring the $AutoRestart and $NoRestart switch parameters defined at lines 13-14. In RMM context, $NoRestart is set to $true at line 66, but this is never checked before restarting.
Proposed fix
Write-Log "Firmware update completed - reboot required"
- # Prompt for restart
- Write-Host "`nThe system will restart in 30 seconds..." -ForegroundColor Yellow
- Write-Host "Press Ctrl+C to cancel restart" -ForegroundColor Yellow
-
- Start-Sleep -Seconds 30
-
- Write-Log "Initiating system restart"
- Restart-Computer -Force
+ if ($NoRestart) {
+ Write-Host "`nRestart suppressed by -NoRestart flag. Please restart manually." -ForegroundColor Yellow
+ Write-Log "Restart suppressed - manual reboot required"
+ } elseif ($AutoRestart) {
+ Write-Log "Initiating automatic system restart"
+ Restart-Computer -Force
+ } else {
+ # Interactive mode - prompt for restart
+ Write-Host "`nThe system will restart in 30 seconds..." -ForegroundColor Yellow
+ Write-Host "Press Ctrl+C to cancel restart" -ForegroundColor Yellow
+ Start-Sleep -Seconds 30
+ Write-Log "Initiating system restart"
+ Restart-Computer -Force
+ }🤖 Prompt for AI Agents
In `@Update-Dell7450Firmware.ps1` around lines 421 - 434, The restart block
unconditionally sleeps and calls Restart-Computer, ignoring the $AutoRestart and
$NoRestart switches; modify the logic around Start-Sleep and Restart-Computer in
the same conditional that logs "Initiating system restart" so it first checks
$NoRestart (skip restart and log/exit appropriately) and then respects
$AutoRestart (if true, perform the sleep+Restart-Computer sequence; if false,
prompt the user and do not auto-restart), updating messages via
Write-Host/Write-Log accordingly and preserving the existing "Installation
failed" branch behavior.
| Write-Log "Script completed" | ||
| Write-Host "`nLog file: $LogFile" | ||
| } | ||
|
|
||
| # Run | ||
| Main |
There was a problem hiding this comment.
Missing Stop-Transcript call.
The script starts a transcript at line 84 but never calls Stop-Transcript. As per coding guidelines, Script Logic Section must be wrapped in Start-Transcript and Stop-Transcript for full logging.
Proposed fix
Write-Log "Script completed"
Write-Host "`nLog file: $LogFile"
+ Stop-Transcript
}
# Run
Main📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Write-Log "Script completed" | |
| Write-Host "`nLog file: $LogFile" | |
| } | |
| # Run | |
| Main | |
| Write-Log "Script completed" | |
| Write-Host "`nLog file: $LogFile" | |
| Stop-Transcript | |
| } | |
| # Run | |
| Main |
🤖 Prompt for AI Agents
In `@Update-Dell7450Firmware.ps1` around lines 443 - 448, The script starts a
transcript with Start-Transcript but never calls Stop-Transcript; ensure you
stop the transcript when the script finishes by adding a Stop-Transcript call
tied to the script lifecycle—either add a Stop-Transcript in the Main function's
cleanup/finally block or invoke Stop-Transcript immediately after the Main call,
and guard it so it only runs if Start-Transcript was used (e.g., check a
$TranscriptStarted flag or use Try/Finally around
Start-Transcript/Stop-Transcript) to guarantee proper logging closure.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.