Files
inno-installers/NetworkDriveManager/NetworkDriveManager.ps1
cproudlock 691c6f9656 Add JT2GO, NetworkDriveManager, and Template projects
JT2GO:
- Inno Setup installer for JT2Go with prerequisite checking
- Checks for VC++ x86/x64 redistributables and .NET 4.8
- Verifies admin privileges before installation
- Supports silent installation mode

NetworkDriveManager:
- Full Inno Setup implementation with integrated PowerShell logic
- Menu-based interface for managing legacy domain network drives
- Features: backup/restore mappings, credential testing, drive reset
- Server migration from rd.ds.ge.com to wjs.geaerospace.net
- Three-phase reconnection to prevent error 1219 with multiple shares
- Add predefined drives (S:, V:, Y:) functionality

Template:
- Reusable Inno Setup project template for co-workers
- Documented sections with examples and best practices
- Includes utility functions and common patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:07:07 -05:00

1262 lines
46 KiB
PowerShell

<#
.SYNOPSIS
Network Drive Manager - Backup, validate credentials, and reconnect SMB shares
.DESCRIPTION
This script helps users manage network drives connected to legacy domain servers.
It backs up current mappings, validates credentials, and reconnects drives after
password changes.
.NOTES
Author: Cameron Proudlock / GE Aerospace
Version: 1.0
Requires: PowerShell 5.1+
#>
#Requires -Version 5.1
# ============================================================================
# CONFIGURATION - Modify these variables as needed
# ============================================================================
# Legacy domain servers to manage (add/remove as needed)
$LegacyServers = @(
"tsgwp00525.rd.ds.ge.com"
# Add additional servers here:
# "server2.rd.ds.ge.com"
# "server3.rd.ds.ge.com"
)
# Legacy domain suffix
$LegacyDomain = "logon.ds.ge.com"
# Password reset URL
$PasswordResetURL = "https://mypassword.ge.com"
# Backup file location (OneDrive Documents)
$OneDrivePath = [Environment]::GetFolderPath('MyDocuments') # Falls back if OneDrive not configured
$BackupFileName = "NetworkDriveMappings.json"
$BackupFilePath = Join-Path -Path $OneDrivePath -ChildPath $BackupFileName
# ============================================================================
# FUNCTIONS
# ============================================================================
function Get-LegacyUsername {
<#
.SYNOPSIS
Derives the legacy username from the current Windows profile
#>
$currentUser = $env:USERNAME
return "$currentUser@$LegacyDomain"
}
function Invoke-PasswordResetFlow {
<#
.SYNOPSIS
Guides user through password reset and re-prompts for new credentials
.OUTPUTS
Returns new SecureString password if successful, $null if cancelled
#>
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host " Password Reset Required" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " Your legacy domain password needs to be reset." -ForegroundColor White
Write-Host ""
Write-Host " Steps to complete:" -ForegroundColor Cyan
Write-Host " 1. Click 'Y' below to open the password reset portal"
Write-Host " 2. Reset your password at $PasswordResetURL"
Write-Host " 3. Return here and type 'DONE'"
Write-Host " 4. Wait for the 10-minute sync timer"
Write-Host " 5. Enter your NEW password to continue"
Write-Host ""
Write-Host " NOTE: Password changes can take up to 10 minutes to sync" -ForegroundColor Gray
Write-Host " from Azure AD to the legacy domain. The script will wait" -ForegroundColor Gray
Write-Host " automatically after you confirm the reset." -ForegroundColor Gray
Write-Host ""
$openPortal = Read-Host "Open password reset portal now? (Y/n)"
if ($openPortal.ToLower() -ne 'n') {
Start-Process $PasswordResetURL
Write-Host ""
Write-Host " Browser opened to password reset portal..." -ForegroundColor Gray
}
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " [ ] I have reset my password at $PasswordResetURL" -ForegroundColor White
Write-Host ""
$confirmed = $false
while (-not $confirmed) {
$confirmation = Read-Host "Type 'DONE' when you have reset your password (or 'CANCEL' to abort)"
switch ($confirmation.ToUpper()) {
"DONE" {
$confirmed = $true
}
"CANCEL" {
Write-Host ""
Write-Host " Password reset cancelled." -ForegroundColor Yellow
return $null
}
default {
Write-Host " Please type 'DONE' or 'CANCEL'" -ForegroundColor Yellow
}
}
}
Write-Host ""
Write-Host " [X] Password reset confirmed!" -ForegroundColor Green
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " Waiting for AD Sync (~10 minutes)" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Your new password needs time to sync to the legacy domain." -ForegroundColor White
Write-Host " Please wait for the timer to complete, or press 'S' to skip" -ForegroundColor White
Write-Host " if you've already waited." -ForegroundColor White
Write-Host ""
# 10 minute countdown timer
$totalSeconds = 600 # 10 minutes
$startTime = Get-Date
$endTime = $startTime.AddSeconds($totalSeconds)
$skipped = $false
# Check if a key is available to read (non-blocking)
$host.UI.RawUI.FlushInputBuffer()
while ((Get-Date) -lt $endTime -and -not $skipped) {
$remaining = $endTime - (Get-Date)
$minutes = [math]::Floor($remaining.TotalMinutes)
$seconds = $remaining.Seconds
# Create progress bar
$elapsed = ((Get-Date) - $startTime).TotalSeconds
$percentComplete = [math]::Min(100, [math]::Floor(($elapsed / $totalSeconds) * 100))
$barLength = 40
$filledLength = [math]::Floor($barLength * $percentComplete / 100)
$bar = ("" * $filledLength) + ("" * ($barLength - $filledLength))
# Write progress on same line
Write-Host "`r [$bar] $($minutes.ToString('00')):$($seconds.ToString('00')) remaining (Press 'S' to skip) " -NoNewline -ForegroundColor Cyan
# Check for keypress (non-blocking)
if ([Console]::KeyAvailable) {
$key = [Console]::ReadKey($true)
if ($key.Key -eq 'S') {
$skipped = $true
Write-Host ""
Write-Host ""
Write-Host " Timer skipped by user." -ForegroundColor Yellow
}
}
Start-Sleep -Milliseconds 500
}
if (-not $skipped) {
Write-Host ""
Write-Host ""
Write-Host " [OK] Sync wait complete!" -ForegroundColor Green
}
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Please enter your NEW password below." -ForegroundColor White
Write-Host ""
$newPassword = Read-Host " Enter your NEW legacy password" -AsSecureString
if ($newPassword.Length -eq 0) {
Write-Host ""
Write-Host " [ERROR] Password cannot be empty." -ForegroundColor Red
return $null
}
# Confirm password
Write-Host ""
$confirmPassword = Read-Host " Confirm your NEW legacy password" -AsSecureString
# Compare the two passwords
$BSTR1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($newPassword)
$BSTR2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmPassword)
$plain1 = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR1)
$plain2 = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR2)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR1)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR2)
if ($plain1 -ne $plain2) {
Write-Host ""
Write-Host " [ERROR] Passwords do not match. Please try again." -ForegroundColor Red
$plain1 = $null
$plain2 = $null
return $null
}
$plain1 = $null
$plain2 = $null
Write-Host ""
Write-Host " [OK] Password confirmed!" -ForegroundColor Green
return $newPassword
}
function Invoke-ZscalerReauthPrompt {
<#
.SYNOPSIS
Prompts user to re-authenticate to Zscaler and retry
.OUTPUTS
Returns $true if user wants to retry, $false to cancel
#>
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host " Zscaler Re-Authentication Required" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " The network path could not be found. This typically means" -ForegroundColor White
Write-Host " your Zscaler connection needs to be re-authenticated." -ForegroundColor White
Write-Host ""
Write-Host " Steps to fix:" -ForegroundColor Cyan
Write-Host " 1. Look for the Zscaler icon in your system tray"
Write-Host " 2. Right-click and select 'Sign In' or 'Authenticate'"
Write-Host " 3. Complete the authentication process"
Write-Host " 4. Return here and select 'Retry'"
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
$retry = Read-Host "Type 'RETRY' after re-authenticating to Zscaler (or 'CANCEL' to abort)"
return ($retry.ToUpper() -eq "RETRY")
}
function Invoke-ErrorActionHandler {
<#
.SYNOPSIS
Handles specific error actions and returns whether to retry
.OUTPUTS
Hashtable with:
- Retry: $true if operation should be retried
- NewPassword: SecureString if password was reset, $null otherwise
- Cancel: $true if user wants to cancel entirely
#>
param(
[Parameter(Mandatory)]
[string]$Action,
[Parameter(Mandatory)]
[int]$ErrorCode,
[string]$AdditionalInfo = ""
)
$result = @{
Retry = $false
NewPassword = $null
Cancel = $false
}
switch ($Action) {
"contact_manager" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host " Access Denied (Error 5)" -ForegroundColor Red
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Write-Host " You do not have permission to access this share." -ForegroundColor White
Write-Host ""
Write-Host " ACTION REQUIRED:" -ForegroundColor Yellow
Write-Host " Contact your manager to request access to this resource."
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Read-Host "Press Enter to continue..."
$result.Cancel = $true
}
"zscaler_reauth" {
$retry = Invoke-ZscalerReauthPrompt
$result.Retry = $retry
$result.Cancel = (-not $retry)
}
"retry_or_zscaler" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host " Connection Dropped (Error 64)" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " The network connection was unexpectedly terminated." -ForegroundColor White
Write-Host ""
Write-Host " This could be caused by:" -ForegroundColor Cyan
Write-Host " - Zscaler connection timeout (most common)"
Write-Host " - Network instability"
Write-Host " - Server-side connection reset"
Write-Host ""
Write-Host " Try re-authenticating to Zscaler first." -ForegroundColor Yellow
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
$retry = Invoke-ZscalerReauthPrompt
$result.Retry = $retry
$result.Cancel = (-not $retry)
}
"verify_path" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host " Invalid Share Path (Error 67)" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " The share path appears to be invalid or malformed." -ForegroundColor White
if ($AdditionalInfo) {
Write-Host " Path: $AdditionalInfo" -ForegroundColor Gray
}
Write-Host ""
Write-Host " Possible causes:" -ForegroundColor Cyan
Write-Host " - The share may have been renamed or removed"
Write-Host " - The path may be incorrectly formatted"
Write-Host " - Typo in the server or share name"
Write-Host ""
Write-Host " ACTION REQUIRED:" -ForegroundColor Yellow
Write-Host " Verify the share path is correct, or contact IT support"
Write-Host " if you believe this share should exist."
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Read-Host "Press Enter to continue..."
# Don't retry automatically - path needs manual verification
$result.Cancel = $true
}
"reassign_drive_letter" {
# This is handled automatically - we'll disconnect and reconnect
Write-Host ""
Write-Host " [INFO] Drive letter conflict detected - will reassign automatically" -ForegroundColor Cyan
$result.Retry = $true
}
"reset_password_flow" {
$newPassword = Invoke-PasswordResetFlow
if ($null -ne $newPassword) {
$result.Retry = $true
$result.NewPassword = $newPassword
}
else {
$result.Cancel = $true
}
}
"clear_connections" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host " Multiple Connections Detected (Error 1219)" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Yellow
Write-Host ""
Write-Host " You have existing connections to this server with different" -ForegroundColor White
Write-Host " credentials. This often happens when:" -ForegroundColor White
Write-Host " - You have mapped drives from a previous session" -ForegroundColor Gray
Write-Host " - Windows cached old credentials" -ForegroundColor Gray
Write-Host " - Another application connected to the server" -ForegroundColor Gray
Write-Host ""
Write-Host " Clearing all connections to legacy servers..." -ForegroundColor Cyan
# Clear all connections to legacy servers
foreach ($server in $LegacyServers) {
# Clear IPC$ connection
$null = net use "\\$server\IPC$" /delete /y 2>&1
# Clear server root
$null = net use "\\$server" /delete /y 2>&1
}
# Clear any mapped drives to legacy servers
$netUseList = net use 2>&1
foreach ($server in $LegacyServers) {
$serverConnections = $netUseList | Select-String -Pattern $server
foreach ($conn in $serverConnections) {
$connString = $conn.ToString()
if ($connString -match '^(OK|Disconnected|Unavailable)?\s*([A-Z]:)') {
$driveLetter = $Matches[2]
Write-Host " Disconnecting $driveLetter..." -NoNewline
$null = net use $driveLetter /delete /y 2>&1
Write-Host " Done" -ForegroundColor Green
}
}
}
# Also clear cached credentials
foreach ($server in $LegacyServers) {
$null = cmdkey /delete:$server 2>&1
$null = cmdkey /delete:"Domain:target=$server" 2>&1
}
Write-Host ""
Write-Host " [OK] Connections cleared. Will retry..." -ForegroundColor Green
Write-Host ""
Start-Sleep -Seconds 2
$result.Retry = $true
}
"contact_helpdesk" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host " Account Issue Detected" -ForegroundColor Red
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Write-Host " Your account has a restriction that prevents connection." -ForegroundColor White
Write-Host " Error Code: $ErrorCode" -ForegroundColor Gray
Write-Host ""
Write-Host " ACTION REQUIRED:" -ForegroundColor Yellow
Write-Host " Please contact the IT Helpdesk for assistance."
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Read-Host "Press Enter to continue..."
$result.Cancel = $true
}
"unknown_error" {
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host " Unknown Error (Code: $ErrorCode)" -ForegroundColor Red
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Write-Host " An unexpected error occurred." -ForegroundColor White
if ($AdditionalInfo) {
Write-Host " Details: $AdditionalInfo" -ForegroundColor Gray
}
Write-Host ""
Write-Host " Options:" -ForegroundColor Cyan
Write-Host " - Try re-authenticating to Zscaler"
Write-Host " - Check your network connection"
Write-Host " - Contact IT Helpdesk if the issue persists"
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
$choice = Read-Host "Type 'RETRY' to try again, or press Enter to cancel"
$result.Retry = ($choice.ToUpper() -eq "RETRY")
$result.Cancel = (-not $result.Retry)
}
default {
$result.Cancel = $true
}
}
return $result
}
function Get-CurrentDriveMappings {
<#
.SYNOPSIS
Gets all current network drive mappings and filters for legacy servers
#>
param(
[switch]$AllDrives,
[string[]]$ServerFilter = $LegacyServers
)
$mappings = @()
# Get all network drives
$netDrives = Get-WmiObject -Class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue
foreach ($drive in $netDrives) {
$mapping = [PSCustomObject]@{
DriveLetter = $drive.DeviceID
RemotePath = $drive.ProviderName
ServerName = if ($drive.ProviderName -match '\\\\([^\\]+)\\') { $Matches[1] } else { $null }
ShareName = if ($drive.ProviderName -match '\\\\[^\\]+\\(.+)$') { $Matches[1] } else { $null }
IsLegacy = $false
}
# Check if this is a legacy server drive
foreach ($server in $ServerFilter) {
if ($mapping.ServerName -like "*$server*" -or $mapping.RemotePath -like "*$server*") {
$mapping.IsLegacy = $true
break
}
}
if ($AllDrives -or $mapping.IsLegacy) {
$mappings += $mapping
}
}
return $mappings
}
function Backup-DriveMappings {
<#
.SYNOPSIS
Saves current drive mappings to a JSON file in OneDrive
#>
param(
[string]$FilePath = $BackupFilePath
)
$mappings = Get-CurrentDriveMappings -AllDrives
$backupData = [PSCustomObject]@{
BackupDate = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
ComputerName = $env:COMPUTERNAME
Username = $env:USERNAME
Mappings = $mappings
}
try {
$backupData | ConvertTo-Json -Depth 3 | Out-File -FilePath $FilePath -Encoding UTF8 -Force
Write-Host "[OK] " -ForegroundColor Green -NoNewline
Write-Host "Backup saved to: $FilePath"
return $true
}
catch {
Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
Write-Host "Failed to save backup: $_"
return $false
}
}
function Get-SavedMappings {
<#
.SYNOPSIS
Loads previously saved drive mappings from backup file
#>
param(
[string]$FilePath = $BackupFilePath
)
if (-not (Test-Path $FilePath)) {
Write-Host "[WARN] " -ForegroundColor Yellow -NoNewline
Write-Host "No backup file found at: $FilePath"
return $null
}
try {
$backupData = Get-Content -Path $FilePath -Raw | ConvertFrom-Json
Write-Host "[OK] " -ForegroundColor Green -NoNewline
Write-Host "Loaded backup from: $($backupData.BackupDate)"
return $backupData
}
catch {
Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
Write-Host "Failed to read backup: $_"
return $null
}
}
function Test-SMBCredentials {
<#
.SYNOPSIS
Tests credentials against a legacy SMB server
.OUTPUTS
Returns a hashtable with Success, ErrorCode, and Message
#>
param(
[Parameter(Mandatory)]
[string]$Server,
[Parameter(Mandatory)]
[string]$Username,
[Parameter(Mandatory)]
[SecureString]$Password
)
$result = @{
Success = $false
ErrorCode = 0
Message = ""
Action = ""
}
# Convert SecureString to plain text for net use (unfortunately required)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
# Try to connect to the server's IPC$ share (doesn't require a specific share to exist)
$testPath = "\\$Server\IPC$"
# First, remove ALL existing connections to this server (not just IPC$)
$null = net use $testPath /delete /y 2>&1
$null = net use "\\$Server" /delete /y 2>&1
# Also try to clear any other connections to this server
$existingConnections = net use 2>&1 | Select-String -Pattern $Server
foreach ($conn in $existingConnections) {
if ($conn -match '([A-Z]:)\s+\\\\') {
$null = net use $Matches[1] /delete /y 2>&1
}
}
# Attempt connection
$netUseOutput = net use $testPath /user:$Username $PlainPassword 2>&1
$exitCode = $LASTEXITCODE
# Parse the actual system error code from the output message
# net use often returns exit code 2 but the real error is in the message
$parsedErrorCode = $exitCode
$outputString = $netUseOutput | Out-String
if ($outputString -match 'System error (\d+)') {
$parsedErrorCode = [int]$Matches[1]
}
elseif ($outputString -match 'error (\d+)') {
$parsedErrorCode = [int]$Matches[1]
}
# Clean up the test connection
$null = net use $testPath /delete /y 2>&1
# Clear password from memory
$PlainPassword = $null
# Interpret the result using parsed error code
switch ($parsedErrorCode) {
0 {
$result.Success = $true
$result.Message = "Credentials validated successfully"
$result.Action = "none"
}
5 {
$result.ErrorCode = 5
$result.Message = "Access denied - you may not have permission to this share"
$result.Action = "contact_manager"
}
53 {
$result.ErrorCode = 53
$result.Message = "Network path not found - Zscaler may need re-authentication"
$result.Action = "zscaler_reauth"
}
64 {
$result.ErrorCode = 64
$result.Message = "Network connection was dropped unexpectedly"
$result.Action = "retry_or_zscaler"
}
67 {
$result.ErrorCode = 67
$result.Message = "Network name cannot be found - invalid share path"
$result.Action = "verify_path"
}
85 {
$result.ErrorCode = 85
$result.Message = "Drive letter is already in use"
$result.Action = "reassign_drive_letter"
}
86 {
$result.ErrorCode = 86
$result.Message = "Invalid password"
$result.Action = "reset_password_flow"
}
1219 {
$result.ErrorCode = 1219
$result.Message = "Multiple connections to server exist with different credentials"
$result.Action = "clear_connections"
}
1326 {
$result.ErrorCode = 1326
$result.Message = "Logon failure - unknown username or bad password"
$result.Action = "reset_password_flow"
}
1327 {
$result.ErrorCode = 1327
$result.Message = "Account restriction - policy is preventing logon"
$result.Action = "contact_helpdesk"
}
1330 {
$result.ErrorCode = 1330
$result.Message = "Password has expired"
$result.Action = "reset_password_flow"
}
1331 {
$result.ErrorCode = 1331
$result.Message = "Account is disabled"
$result.Action = "contact_helpdesk"
}
1909 {
$result.ErrorCode = 1909
$result.Message = "Account is locked out"
$result.Action = "reset_password_flow"
}
default {
$result.ErrorCode = $parsedErrorCode
$result.Message = "Unknown error (code: $parsedErrorCode) - $outputString"
$result.Action = "unknown_error"
}
}
return $result
}
function Remove-LegacyDriveMappings {
<#
.SYNOPSIS
Removes all network drive mappings for legacy servers
#>
param(
[string[]]$Servers = $LegacyServers
)
$currentMappings = Get-CurrentDriveMappings
$removed = @()
foreach ($mapping in $currentMappings) {
if ($mapping.IsLegacy) {
Write-Host " Removing $($mapping.DriveLetter) ($($mapping.RemotePath))... " -NoNewline
$result = net use $mapping.DriveLetter /delete /y 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "OK" -ForegroundColor Green
$removed += $mapping
}
else {
Write-Host "Failed" -ForegroundColor Red
}
}
}
# Also clear any cached connections to these servers
foreach ($server in $Servers) {
$null = net use "\\$server\IPC$" /delete /y 2>&1
}
return $removed
}
function Remove-WindowsCredentials {
<#
.SYNOPSIS
Removes stored Windows credentials for legacy servers
#>
param(
[string[]]$Servers = $LegacyServers
)
foreach ($server in $Servers) {
Write-Host " Clearing credentials for $server... " -NoNewline
# Try different credential target formats
$targets = @(
$server,
"Domain:target=$server",
"LegacyGeneric:target=$server",
"*$server*"
)
$cleared = $false
foreach ($target in $targets) {
$result = cmdkey /delete:$target 2>&1
if ($LASTEXITCODE -eq 0) {
$cleared = $true
}
}
if ($cleared) {
Write-Host "OK" -ForegroundColor Green
}
else {
Write-Host "Not found/Already cleared" -ForegroundColor Yellow
}
}
}
function Add-DriveMappings {
<#
.SYNOPSIS
Reconnects drive mappings with new credentials
Handles error 85 (drive letter in use) automatically
#>
param(
[Parameter(Mandatory)]
[PSCustomObject[]]$Mappings,
[Parameter(Mandatory)]
[string]$Username,
[Parameter(Mandatory)]
[SecureString]$Password
)
# Convert SecureString to plain text
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$results = @()
foreach ($mapping in $Mappings) {
if (-not $mapping.IsLegacy) { continue }
Write-Host " Mapping $($mapping.DriveLetter) to $($mapping.RemotePath)... " -NoNewline
# First attempt
$netUseOutput = net use $mapping.DriveLetter $mapping.RemotePath /user:$Username $PlainPassword /persistent:yes 2>&1
$exitCode = $LASTEXITCODE
# Handle error 85 - drive letter already in use
if ($exitCode -eq 85) {
Write-Host "in use, reassigning... " -NoNewline -ForegroundColor Yellow
# Try to disconnect the existing mapping
$null = net use $mapping.DriveLetter /delete /y 2>&1
Start-Sleep -Milliseconds 500
# Retry the mapping
$netUseOutput = net use $mapping.DriveLetter $mapping.RemotePath /user:$Username $PlainPassword /persistent:yes 2>&1
$exitCode = $LASTEXITCODE
}
$mapResult = [PSCustomObject]@{
DriveLetter = $mapping.DriveLetter
RemotePath = $mapping.RemotePath
Success = ($exitCode -eq 0)
ErrorCode = $exitCode
Message = $netUseOutput
}
if ($exitCode -eq 0) {
Write-Host "OK" -ForegroundColor Green
}
else {
Write-Host "Failed (Error: $exitCode)" -ForegroundColor Red
}
$results += $mapResult
}
# Clear password from memory
$PlainPassword = $null
return $results
}
function Show-Menu {
<#
.SYNOPSIS
Displays the main menu
#>
Clear-Host
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " GE Aerospace - Network Drive Manager" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Current User: $env:USERNAME" -ForegroundColor Gray
Write-Host " Legacy User: $(Get-LegacyUsername)" -ForegroundColor Gray
Write-Host " Backup File: $BackupFilePath" -ForegroundColor Gray
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " 1. " -NoNewline -ForegroundColor Yellow
Write-Host "View current network drive mappings"
Write-Host " 2. " -NoNewline -ForegroundColor Yellow
Write-Host "Backup current mappings to OneDrive"
Write-Host " 3. " -NoNewline -ForegroundColor Yellow
Write-Host "Test legacy credentials"
Write-Host " 4. " -NoNewline -ForegroundColor Yellow
Write-Host "Full reset - Disconnect, clear creds, and reconnect drives"
Write-Host " 5. " -NoNewline -ForegroundColor Yellow
Write-Host "Restore drives from backup"
Write-Host " 6. " -NoNewline -ForegroundColor Yellow
Write-Host "Open password reset portal"
Write-Host ""
Write-Host " Q. " -NoNewline -ForegroundColor Red
Write-Host "Quit"
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
}
function Invoke-FullReset {
<#
.SYNOPSIS
Performs full credential reset and drive reconnection
With intelligent error handling and retry logic
#>
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " Full Credential Reset & Drive Reconnection" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
# Step 1: Backup current mappings
Write-Host "[Step 1/5] Backing up current drive mappings..." -ForegroundColor Yellow
$backupSuccess = Backup-DriveMappings
if (-not $backupSuccess) {
Write-Host ""
$continue = Read-Host "Backup failed. Continue anyway? (y/N)"
if ($continue -ne 'y') { return }
}
# Load the backup we just made (or existing one)
$savedMappings = Get-SavedMappings
$legacyMappings = $savedMappings.Mappings | Where-Object { $_.IsLegacy -eq $true }
if (-not $legacyMappings -or $legacyMappings.Count -eq 0) {
Write-Host ""
Write-Host "[WARN] " -ForegroundColor Yellow -NoNewline
Write-Host "No legacy drive mappings found to restore."
Write-Host " Press Enter to return to menu..."
Read-Host
return
}
Write-Host ""
Write-Host "Found $($legacyMappings.Count) legacy drive(s) to manage:" -ForegroundColor Cyan
foreach ($map in $legacyMappings) {
Write-Host " $($map.DriveLetter) -> $($map.RemotePath)" -ForegroundColor Gray
}
Write-Host ""
# Step 2: Get credentials from user
Write-Host "[Step 2/5] Enter your legacy domain credentials" -ForegroundColor Yellow
$legacyUser = Get-LegacyUsername
Write-Host " Username: $legacyUser" -ForegroundColor Gray
Write-Host ""
$password = Read-Host " Enter your legacy password" -AsSecureString
if ($password.Length -eq 0) {
Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
Write-Host "Password cannot be empty."
Read-Host "Press Enter to return to menu..."
return
}
# Step 3: Test credentials first (with retry loop for errors)
$credentialsValid = $false
$maxRetries = 3
$retryCount = 0
while (-not $credentialsValid -and $retryCount -lt $maxRetries) {
Write-Host ""
Write-Host "[Step 3/5] Testing credentials against $($LegacyServers[0])..." -ForegroundColor Yellow
$testResult = Test-SMBCredentials -Server $LegacyServers[0] -Username $legacyUser -Password $password
if ($testResult.Success) {
$credentialsValid = $true
Write-Host "[OK] " -ForegroundColor Green -NoNewline
Write-Host "Credentials validated successfully!"
}
else {
Write-Host ""
Write-Host "[FAILED] " -ForegroundColor Red -NoNewline
Write-Host $testResult.Message
# Handle the specific error
$errorHandler = Invoke-ErrorActionHandler -Action $testResult.Action -ErrorCode $testResult.ErrorCode
if ($errorHandler.Cancel) {
Write-Host ""
Write-Host "Operation cancelled." -ForegroundColor Yellow
Read-Host "Press Enter to return to menu..."
return
}
if ($errorHandler.NewPassword) {
# User reset their password - use the new one
$password = $errorHandler.NewPassword
Write-Host ""
Write-Host " Retrying with new password..." -ForegroundColor Cyan
}
elseif ($errorHandler.Retry) {
# Retry with same credentials (e.g., after Zscaler reauth)
Write-Host ""
Write-Host " Retrying..." -ForegroundColor Cyan
}
$retryCount++
}
}
if (-not $credentialsValid) {
Write-Host ""
Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
Write-Host "Maximum retry attempts reached. Please try again later."
Read-Host "Press Enter to return to menu..."
return
}
Write-Host ""
# Step 4: Remove existing mappings and credentials
Write-Host "[Step 4/5] Removing existing mappings and credentials..." -ForegroundColor Yellow
Remove-LegacyDriveMappings
Remove-WindowsCredentials
Write-Host ""
# Step 5: Reconnect drives (with individual error handling)
Write-Host "[Step 5/5] Reconnecting drives with new credentials..." -ForegroundColor Yellow
$mapResults = Add-DriveMappings -Mappings $legacyMappings -Username $legacyUser -Password $password
# Check for failures that need individual handling
$failedMappings = $mapResults | Where-Object { -not $_.Success }
foreach ($failed in $failedMappings) {
Write-Host ""
Write-Host " Handling failed mapping: $($failed.DriveLetter)" -ForegroundColor Yellow
# Determine the action based on error code
$action = switch ($failed.ErrorCode) {
5 { "contact_manager" }
53 { "zscaler_reauth" }
64 { "retry_or_zscaler" }
67 { "verify_path" }
86 { "reset_password_flow" }
1326 { "reset_password_flow" }
1330 { "reset_password_flow" }
1909 { "reset_password_flow" }
default { "unknown_error" }
}
$errorHandler = Invoke-ErrorActionHandler -Action $action -ErrorCode $failed.ErrorCode -AdditionalInfo $failed.RemotePath
if ($errorHandler.Retry -and -not $errorHandler.Cancel) {
if ($errorHandler.NewPassword) {
$password = $errorHandler.NewPassword
}
# Retry just this one mapping
Write-Host " Retrying $($failed.DriveLetter)... " -NoNewline
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$retryOutput = net use $failed.DriveLetter $failed.RemotePath /user:$legacyUser $PlainPassword /persistent:yes 2>&1
$PlainPassword = $null
if ($LASTEXITCODE -eq 0) {
Write-Host "OK" -ForegroundColor Green
# Update the result
$failed.Success = $true
$failed.ErrorCode = 0
}
else {
Write-Host "Still failed (Error: $LASTEXITCODE)" -ForegroundColor Red
}
}
}
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " Summary" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
$successCount = ($mapResults | Where-Object { $_.Success }).Count
$failCount = ($mapResults | Where-Object { -not $_.Success }).Count
Write-Host ""
Write-Host " Successful: " -NoNewline
Write-Host $successCount -ForegroundColor Green
Write-Host " Failed: " -NoNewline
Write-Host $failCount -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
Write-Host ""
if ($failCount -gt 0) {
Write-Host "Failed mappings:" -ForegroundColor Red
foreach ($result in ($mapResults | Where-Object { -not $_.Success })) {
Write-Host " $($result.DriveLetter) - Error $($result.ErrorCode)" -ForegroundColor Red
}
Write-Host ""
Write-Host " You may need to map these drives manually or contact IT support." -ForegroundColor Yellow
}
Write-Host ""
Read-Host "Press Enter to return to menu..."
}
# ============================================================================
# MAIN SCRIPT
# ============================================================================
# Check if running interactively or with parameters
param(
[switch]$Silent,
[switch]$BackupOnly,
[switch]$Reset
)
if ($BackupOnly) {
Backup-DriveMappings
exit 0
}
if ($Reset) {
# Non-interactive reset - would need credentials passed in or prompted
Write-Host "Non-interactive reset not yet implemented. Please run without -Reset for interactive mode."
exit 1
}
# ============================================================================
# AUTO-BACKUP ON STARTUP
# ============================================================================
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " GE Aerospace - Network Drive Manager" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
# Check for existing backup and auto-backup
$doBackup = $true
if (Test-Path $BackupFilePath) {
# Backup exists - ask if user wants to overwrite
try {
$existingBackup = Get-Content -Path $BackupFilePath -Raw | ConvertFrom-Json
$backupDate = $existingBackup.BackupDate
Write-Host " Existing backup found from: $backupDate" -ForegroundColor Yellow
Write-Host ""
$overwrite = Read-Host " Overwrite existing backup with current mappings? (y/N)"
$doBackup = ($overwrite.ToLower() -eq 'y')
}
catch {
Write-Host " Existing backup file is corrupted. Creating new backup..." -ForegroundColor Yellow
$doBackup = $true
}
}
else {
Write-Host " No existing backup found. Creating automatic backup..." -ForegroundColor Cyan
}
if ($doBackup) {
$currentMappings = Get-CurrentDriveMappings -AllDrives
$legacyCount = ($currentMappings | Where-Object { $_.IsLegacy }).Count
if ($currentMappings.Count -gt 0) {
Backup-DriveMappings | Out-Null
Write-Host " [OK] Backed up $($currentMappings.Count) drive(s) ($legacyCount legacy)" -ForegroundColor Green
}
else {
Write-Host " [INFO] No network drives to backup" -ForegroundColor Yellow
}
}
else {
Write-Host " [SKIP] Keeping existing backup" -ForegroundColor Gray
}
Write-Host ""
Start-Sleep -Seconds 1
# Interactive menu loop
do {
Show-Menu
$choice = Read-Host "Select an option"
switch ($choice.ToLower()) {
"1" {
# View current mappings
Write-Host ""
Write-Host "Current Network Drive Mappings:" -ForegroundColor Cyan
Write-Host "================================" -ForegroundColor Cyan
$mappings = Get-CurrentDriveMappings -AllDrives
if ($mappings.Count -eq 0) {
Write-Host " No network drives mapped." -ForegroundColor Yellow
}
else {
foreach ($map in $mappings) {
$legacyTag = if ($map.IsLegacy) { " [LEGACY]" } else { "" }
$color = if ($map.IsLegacy) { "Yellow" } else { "Gray" }
Write-Host " $($map.DriveLetter) -> $($map.RemotePath)$legacyTag" -ForegroundColor $color
}
}
Write-Host ""
Read-Host "Press Enter to continue..."
}
"2" {
# Backup mappings
Write-Host ""
Backup-DriveMappings
Write-Host ""
Read-Host "Press Enter to continue..."
}
"3" {
# Test credentials
Write-Host ""
Write-Host "Testing Legacy Credentials" -ForegroundColor Cyan
Write-Host "==========================" -ForegroundColor Cyan
$legacyUser = Get-LegacyUsername
Write-Host "Username: $legacyUser" -ForegroundColor Gray
Write-Host ""
$password = Read-Host "Enter your legacy password" -AsSecureString
Write-Host ""
Write-Host "Testing against servers..." -ForegroundColor Yellow
foreach ($server in $LegacyServers) {
Write-Host " $server... " -NoNewline
$result = Test-SMBCredentials -Server $server -Username $legacyUser -Password $password
if ($result.Success) {
Write-Host "OK" -ForegroundColor Green
}
else {
Write-Host "FAILED - $($result.Message)" -ForegroundColor Red
}
}
Write-Host ""
Read-Host "Press Enter to continue..."
}
"4" {
# Full reset
Invoke-FullReset
}
"5" {
# Restore from backup
Write-Host ""
$savedMappings = Get-SavedMappings
if ($savedMappings) {
Write-Host ""
Write-Host "Saved mappings from $($savedMappings.BackupDate):" -ForegroundColor Cyan
foreach ($map in $savedMappings.Mappings) {
$legacyTag = if ($map.IsLegacy) { " [LEGACY]" } else { "" }
Write-Host " $($map.DriveLetter) -> $($map.RemotePath)$legacyTag"
}
Write-Host ""
$restore = Read-Host "Restore legacy drives from this backup? (y/N)"
if ($restore -eq 'y') {
$legacyUser = Get-LegacyUsername
Write-Host ""
Write-Host "Username: $legacyUser" -ForegroundColor Gray
$password = Read-Host "Enter your legacy password" -AsSecureString
$legacyMappings = $savedMappings.Mappings | Where-Object { $_.IsLegacy }
Add-DriveMappings -Mappings $legacyMappings -Username $legacyUser -Password $password
}
}
Write-Host ""
Read-Host "Press Enter to continue..."
}
"6" {
# Open password portal
Write-Host ""
Write-Host "Opening $PasswordResetURL ..." -ForegroundColor Cyan
Start-Process $PasswordResetURL
Start-Sleep -Seconds 2
}
"q" {
Write-Host ""
Write-Host "Goodbye!" -ForegroundColor Cyan
}
default {
Write-Host ""
Write-Host "Invalid option. Please try again." -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
} while ($choice.ToLower() -ne 'q')