; ============================================================================ ; GE Aerospace Network Drive Manager - Full Inno Setup Implementation ; All PowerShell logic integrated directly into Inno Setup ; Version 2.0 ; ============================================================================ [Setup] AppId={{E3F4A5B6-C7D8-9E0F-1A2B-3C4D5E6F7890}} AppName=GE Aerospace Network Drive Manager AppVersion=2.0 AppPublisher=WJDT AppPublisherURL=http://tsgwp00525.rd.ds.ge.com DefaultDirName={tmp}\NetworkDriveManager CreateAppDir=no PrivilegesRequired=lowest OutputDir=Output OutputBaseFilename=NetworkDriveManager SolidCompression=yes WizardStyle=modern SetupIconFile=gea-logo.ico WizardImageFile=patrick.bmp WizardSmallImageFile=patrick-sm.bmp DisableWelcomePage=yes DisableDirPage=yes DisableProgramGroupPage=yes DisableReadyPage=yes DisableFinishedPage=yes Uninstallable=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Code] // ============================================================================ // CONSTANTS AND CONFIGURATION // ============================================================================ const // Legacy domain servers to manage // OLD_SERVER is detected in existing mappings, NEW_SERVER is used when remapping OLD_SERVER_1 = 'tsgwp00525.rd.ds.ge.com'; NEW_SERVER_1 = 'tsgwp00525.wjs.geaerospace.net'; // Add more servers as needed: // OLD_SERVER_2 = 'server2.rd.ds.ge.com'; // NEW_SERVER_2 = 'server2.newdomain.com'; LEGACY_DOMAIN = 'logon.ds.ge.com'; PASSWORD_RESET_URL = 'https://mypassword.ge.com'; BACKUP_FILENAME = 'NetworkDriveMappings.json'; // Menu options MENU_VIEW_MAPPINGS = 1; MENU_BACKUP = 2; MENU_TEST_CREDS = 3; MENU_FULL_RESET = 4; MENU_RESTORE = 5; MENU_ADD_DRIVES = 6; MENU_PASSWORD_PORTAL = 7; MENU_QUIT = 8; // Predefined drive mappings DRIVE_S_PATH = '\\tsgwp00525.wjs.geaerospace.net\shared'; DRIVE_S_DESC = 'S: - Shared Drive (Standard)'; DRIVE_V_PATH = '\\tsgwp00525.wjs.geaerospace.net\eng_apps'; DRIVE_V_DESC = 'V: - Engineering Apps (Engineers)'; DRIVE_Y_SERVER = 'win.cighpeifep036.logon.ds.ge.com'; DRIVE_Y_PATH = '\\win.cighpeifep036.logon.ds.ge.com\ugcam_res_lib'; DRIVE_Y_DESC = 'Y: - UGCAM Resource Library (Engineers)'; // Newline constant for script building NL = #13#10; // ============================================================================ // GLOBAL VARIABLES // ============================================================================ var MainMenuPage: TWizardPage; PasswordPage: TInputQueryWizardPage; ProgressPage: TOutputProgressWizardPage; ResultsPage: TOutputMsgMemoWizardPage; MenuListBox: TNewListBox; StatusLabel: TLabel; MappingsListBox: TNewListBox; CurrentPassword: String; LegacyUsername: String; BackupFilePath: String; SelectedMenuOption: Integer; // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ function GetLegacyUsername: String; begin Result := GetUserNameString + '@' + LEGACY_DOMAIN; end; function GetBackupFilePath: String; var LogDir: String; begin LogDir := ExpandConstant('{userdocs}\wjdt\logs'); ForceDirectories(LogDir); Result := LogDir + '\' + BACKUP_FILENAME; end; function GetLegacyServers: TArrayOfString; begin // Return NEW server for credential testing (that's what we'll connect to) SetArrayLength(Result, 1); Result[0] := NEW_SERVER_1; end; // Execute a command and capture output to a file function ExecWithOutput(const Cmd, Params, WorkDir: String; var Output: String): Integer; var TempFile: String; ResultCode: Integer; OutputAnsi: AnsiString; begin TempFile := ExpandConstant('{tmp}\cmd_output_') + IntToStr(Random(99999)) + '.txt'; Exec('cmd.exe', '/c ' + Cmd + ' ' + Params + ' > "' + TempFile + '" 2>&1', WorkDir, SW_HIDE, ewWaitUntilTerminated, ResultCode); if FileExists(TempFile) then begin if LoadStringFromFile(TempFile, OutputAnsi) then Output := String(OutputAnsi) else Output := ''; DeleteFile(TempFile); end else Output := ''; Result := ResultCode; end; // Execute PowerShell command and capture output function ExecPowerShell(const Script: String; var Output: String): Integer; var TempScript, TempOutput: String; ResultCode: Integer; OutputAnsi: AnsiString; CmdLine: String; begin TempScript := ExpandConstant('{tmp}\ps_script_') + IntToStr(Random(99999)) + '.ps1'; TempOutput := ExpandConstant('{tmp}\ps_output_') + IntToStr(Random(99999)) + '.txt'; // Save script to temp file if not SaveStringToFile(TempScript, Script, False) then begin Output := 'ERROR: Could not save script to temp file'; Result := -1; Exit; end; // Build command line - must use cmd.exe for redirection to work CmdLine := '/c powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' + TempScript + '" > "' + TempOutput + '" 2>&1'; // Execute via cmd.exe if not Exec('cmd.exe', CmdLine, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin Output := 'ERROR: Could not execute PowerShell'; Result := -1; DeleteFile(TempScript); Exit; end; // Read output if FileExists(TempOutput) then begin if LoadStringFromFile(TempOutput, OutputAnsi) then Output := String(OutputAnsi) else Output := 'ERROR: Could not read output file'; DeleteFile(TempOutput); end else Output := 'ERROR: No output file created'; DeleteFile(TempScript); Result := ResultCode; end; // Parse error code from net use output function ParseNetUseErrorCode(const Output: String; ExitCode: Integer): Integer; var ErrorPos: Integer; ErrorStr: String; I: Integer; begin Result := ExitCode; // Look for "System error XXXX" ErrorPos := Pos('System error ', Output); if ErrorPos > 0 then begin ErrorStr := ''; for I := ErrorPos + 13 to Length(Output) do begin if (Output[I] >= '0') and (Output[I] <= '9') then ErrorStr := ErrorStr + Output[I] else Break; end; if ErrorStr <> '' then Result := StrToIntDef(ErrorStr, ExitCode); end else begin // Look for "error XXXX" ErrorPos := Pos('error ', Output); if ErrorPos > 0 then begin ErrorStr := ''; for I := ErrorPos + 6 to Length(Output) do begin if (Output[I] >= '0') and (Output[I] <= '9') then ErrorStr := ErrorStr + Output[I] else Break; end; if ErrorStr <> '' then Result := StrToIntDef(ErrorStr, ExitCode); end; end; end; // Get error message for error code function GetErrorMessage(ErrorCode: Integer): String; begin case ErrorCode of 0: Result := 'Success'; 5: Result := 'Access denied - you may not have permission to this share'; 53: Result := 'Network path not found - Zscaler may need re-authentication'; 64: Result := 'Network connection was dropped unexpectedly'; 67: Result := 'Network name cannot be found - invalid share path'; 85: Result := 'Drive letter is already in use'; 86: Result := 'Invalid password'; 1219: Result := 'Multiple connections to server exist with different credentials'; 1326: Result := 'Logon failure - unknown username or bad password'; 1327: Result := 'Account restriction - policy is preventing logon'; 1330: Result := 'Password has expired'; 1331: Result := 'Account is disabled'; 1909: Result := 'Account is locked out'; else Result := 'Unknown error (code: ' + IntToStr(ErrorCode) + ')'; end; end; // Get action for error code function GetErrorAction(ErrorCode: Integer): String; begin case ErrorCode of 5: Result := 'contact_manager'; 53: Result := 'zscaler_reauth'; 64: Result := 'retry_or_zscaler'; 67: Result := 'verify_path'; 85: Result := 'reassign_drive'; 86, 1326, 1330, 1909: Result := 'reset_password'; 1219: Result := 'clear_connections'; 1327, 1331: Result := 'contact_helpdesk'; else Result := 'unknown'; end; end; // ============================================================================ // DRIVE MAPPING FUNCTIONS // ============================================================================ function GetCurrentDriveMappings(LegacyOnly: Boolean): String; var Script: String; Output: String; begin Script := '# Detect old, new, and Y: drive server names' + NL + '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + '$mappings = @()' + NL + '$netDrives = Get-WmiObject -Class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue' + NL + 'foreach ($drive in $netDrives) {' + NL + ' $isLegacy = $false' + NL + ' foreach ($server in $LegacyServers) {' + NL + ' if ($drive.ProviderName -like "*$server*") { $isLegacy = $true; break }' + NL + ' }' + NL; if LegacyOnly then Script := Script + ' if ($isLegacy) {' + NL else Script := Script + ' if ($true) {' + NL; Script := Script + ' $tag = if ($isLegacy) { " [LEGACY]" } else { "" }' + NL + ' $mappings += "$($drive.DeviceID) -> $($drive.ProviderName)$tag"' + NL + ' }' + NL + '}' + NL + 'if ($mappings.Count -eq 0) { Write-Output "No network drives mapped." }' + NL + 'else { $mappings | ForEach-Object { Write-Output $_ } }'; ExecPowerShell(Script, Output); Result := Output; end; function GetDriveMappingsAsJSON: String; var Script: String; Output: String; begin Script := '# Detect old, new, and Y: drive server names' + NL + '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + '$mappings = @()' + NL + '$netDrives = Get-WmiObject -Class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue' + NL + 'foreach ($drive in $netDrives) {' + NL + ' $isLegacy = $false' + NL + ' foreach ($server in $LegacyServers) {' + NL + ' if ($drive.ProviderName -like "*$server*") { $isLegacy = $true; break }' + NL + ' }' + NL + ' $serverName = if ($drive.ProviderName -match "\\\\([^\\]+)\\") { $Matches[1] } else { "" }' + NL + ' $shareName = if ($drive.ProviderName -match "\\\\[^\\]+\\(.+)$") { $Matches[1] } else { "" }' + NL + ' $mappings += [PSCustomObject]@{' + NL + ' DriveLetter = $drive.DeviceID' + NL + ' RemotePath = $drive.ProviderName' + NL + ' ServerName = $serverName' + NL + ' ShareName = $shareName' + NL + ' IsLegacy = $isLegacy' + NL + ' }' + NL + '}' + NL + '$backup = [PSCustomObject]@{' + NL + ' BackupDate = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")' + NL + ' ComputerName = $env:COMPUTERNAME' + NL + ' Username = $env:USERNAME' + NL + ' Mappings = $mappings' + NL + '}' + NL + '$backup | ConvertTo-Json -Depth 3'; ExecPowerShell(Script, Output); Result := Output; end; // ============================================================================ // BACKUP/RESTORE FUNCTIONS // ============================================================================ function BackupDriveMappings: Boolean; var JSONContent: String; begin Result := False; JSONContent := GetDriveMappingsAsJSON; if JSONContent <> '' then begin Result := SaveStringToFile(BackupFilePath, JSONContent, False); end; end; function LoadBackupInfo(var BackupDate, MappingsList: String): Boolean; var Script: String; Output: String; JSONContent: AnsiString; begin Result := False; BackupDate := ''; MappingsList := ''; if not FileExists(BackupFilePath) then Exit; if not LoadStringFromFile(BackupFilePath, JSONContent) then Exit; Script := '$json = Get-Content -Path "' + BackupFilePath + '" -Raw | ConvertFrom-Json' + NL + 'Write-Output "DATE:$($json.BackupDate)"' + NL + 'foreach ($m in $json.Mappings) {' + NL + ' $tag = if ($m.IsLegacy) { " [LEGACY]" } else { "" }' + NL + ' Write-Output "MAP:$($m.DriveLetter) -> $($m.RemotePath)$tag"' + NL + '}'; if ExecPowerShell(Script, Output) = 0 then begin // Parse output if Pos('DATE:', Output) > 0 then begin BackupDate := Copy(Output, Pos('DATE:', Output) + 5, 19); MappingsList := Output; Result := True; end; end; end; // ============================================================================ // CREDENTIAL TESTING // ============================================================================ function TestCredentials(const Server, Username, Password: String; var ErrorCode: Integer; var ErrorMsg: String): Boolean; var Output: String; ExitCode: Integer; TestPath: String; begin Result := False; TestPath := '\\' + Server + '\IPC$'; // First clear any existing connections ExecWithOutput('net', 'use "' + TestPath + '" /delete /y', '', Output); ExecWithOutput('net', 'use "\\' + Server + '" /delete /y', '', Output); // Try to connect ExitCode := ExecWithOutput('net', 'use "' + TestPath + '" /user:' + Username + ' "' + Password + '"', '', Output); // Parse actual error code from output ErrorCode := ParseNetUseErrorCode(Output, ExitCode); // Clean up test connection ExecWithOutput('net', 'use "' + TestPath + '" /delete /y', '', Output); if ErrorCode = 0 then begin Result := True; ErrorMsg := 'Credentials validated successfully'; end else begin ErrorMsg := GetErrorMessage(ErrorCode); end; end; // ============================================================================ // CONNECTION MANAGEMENT // ============================================================================ function ClearAllLegacyConnections: String; var Script: String; Output: String; begin Script := '# Include old, new, and Y: drive server names' + NL + '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + '$results = @()' + NL + NL + '# Clear IPC$ and server connections' + NL + 'foreach ($server in $LegacyServers) {' + NL + ' $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL + ' $null = net use "\\$server" /delete /y 2>&1' + NL + '}' + NL + NL + '# Clear mapped drives' + NL + '$netUseList = net use 2>&1' + NL + 'foreach ($server in $LegacyServers) {' + NL + ' $connections = $netUseList | Select-String -Pattern $server' + NL + ' foreach ($conn in $connections) {' + NL + ' if ($conn -match "^(OK|Disconnected|Unavailable)?\s*([A-Z]:)") {' + NL + ' $drive = $Matches[2]' + NL + ' $null = net use $drive /delete /y 2>&1' + NL + ' $results += "Disconnected $drive"' + NL + ' }' + NL + ' }' + NL + '}' + NL + NL + '# Clear cached credentials' + NL + 'foreach ($server in $LegacyServers) {' + NL + ' $null = cmdkey /delete:$server 2>&1' + NL + ' $null = cmdkey /delete:"Domain:target=$server" 2>&1' + NL + ' $results += "Cleared credentials for $server"' + NL + '}' + NL + NL + 'if ($results.Count -eq 0) { Write-Output "No connections to clear" }' + NL + 'else { $results | ForEach-Object { Write-Output $_ } }'; ExecPowerShell(Script, Output); Result := Output; end; function ReconnectDrivesFromBackup(const Username, Password: String): String; var Script: String; Output: String; begin Script := '$Username = "' + Username + '"' + NL + '$Password = "' + Password + '"' + NL + '$BackupFile = "' + BackupFilePath + '"' + NL + NL + '# Server name mapping (old -> new)' + NL + '$ServerMapping = @{' + NL + ' "' + OLD_SERVER_1 + '" = "' + NEW_SERVER_1 + '"' + NL + '}' + NL + NL + '# All managed servers' + NL + '$ManagedServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + NL + 'if (-not (Test-Path $BackupFile)) {' + NL + ' Write-Output "ERROR: No backup file found"' + NL + ' exit 1' + NL + '}' + NL + NL + '$backup = Get-Content $BackupFile -Raw | ConvertFrom-Json' + NL + '$results = @()' + NL + NL + '# PHASE 1: Collect all legacy mappings and prepare new paths' + NL + '$drivesToMap = @()' + NL + 'foreach ($mapping in $backup.Mappings) {' + NL + ' if ($mapping.IsLegacy) {' + NL + ' $drive = $mapping.DriveLetter' + NL + ' $path = $mapping.RemotePath' + NL + ' $originalPath = $path' + NL + NL + ' # Replace old server names with new ones' + NL + ' foreach ($oldServer in $ServerMapping.Keys) {' + NL + ' $newServer = $ServerMapping[$oldServer]' + NL + ' if ($path -like "*$oldServer*") {' + NL + ' $path = $path -replace [regex]::Escape($oldServer), $newServer' + NL + ' $results += "MIGRATING: $originalPath -> $path"' + NL + ' }' + NL + ' }' + NL + NL + ' $drivesToMap += [PSCustomObject]@{' + NL + ' DriveLetter = $drive' + NL + ' Path = $path' + NL + ' }' + NL + ' }' + NL + '}' + NL + NL + 'if ($drivesToMap.Count -eq 0) {' + NL + ' Write-Output "No legacy drives to reconnect"' + NL + ' exit 0' + NL + '}' + NL + NL + '# PHASE 2: Disconnect ALL drives to managed servers FIRST' + NL + '# This prevents error 1219 when multiple drives point to same server' + NL + '$results += ""' + NL + '$results += "Disconnecting existing drives..."' + NL + 'foreach ($driveInfo in $drivesToMap) {' + NL + ' $null = net use $driveInfo.DriveLetter /delete /y 2>&1' + NL + '}' + NL + NL + '# Also clear IPC$ connections to prevent 1219' + NL + 'foreach ($server in $ManagedServers) {' + NL + ' $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL + ' $null = net use "\\$server" /delete /y 2>&1' + NL + '}' + NL + NL + '# Clear cached credentials' + NL + 'foreach ($server in $ManagedServers) {' + NL + ' $null = cmdkey /delete:$server 2>&1' + NL + '}' + NL + NL + '# Small delay to let Windows release connections' + NL + 'Start-Sleep -Seconds 2' + NL + NL + '# PHASE 3: Reconnect ALL drives' + NL + '$results += ""' + NL + '$results += "Reconnecting drives..."' + NL + 'foreach ($driveInfo in $drivesToMap) {' + NL + ' $drive = $driveInfo.DriveLetter' + NL + ' $path = $driveInfo.Path' + NL + NL + ' $output = net use $drive $path /user:$Username $Password /persistent:yes 2>&1' + NL + ' $exitCode = $LASTEXITCODE' + NL + NL + ' if ($exitCode -eq 0) {' + NL + ' $results += "OK: $drive -> $path"' + NL + ' } else {' + NL + ' $errCode = $exitCode' + NL + ' $outStr = $output | Out-String' + NL + ' if ($outStr -match "System error (\d+)") { $errCode = [int]$Matches[1] }' + NL + ' $results += "FAILED: $drive -> $path (Error $errCode)"' + NL + ' }' + NL + '}' + NL + NL + '$results | ForEach-Object { Write-Output $_ }'; ExecPowerShell(Script, Output); Result := Output; end; // ============================================================================ // UI HELPER FUNCTIONS // ============================================================================ procedure UpdateStatusLabel(const Msg: String); begin if StatusLabel <> nil then StatusLabel.Caption := Msg; end; procedure ShowResultsInMemo(const Title, Content: String); var ResultForm: TSetupForm; Memo: TNewMemo; OKButton: TNewButton; begin // Create a modal results dialog ResultForm := CreateCustomForm; ResultForm.Caption := Title; ResultForm.ClientWidth := 500; ResultForm.ClientHeight := 400; ResultForm.Position := poScreenCenter; Memo := TNewMemo.Create(ResultForm); Memo.Parent := ResultForm; Memo.Left := 10; Memo.Top := 10; Memo.Width := ResultForm.ClientWidth - 20; Memo.Height := ResultForm.ClientHeight - 60; Memo.ScrollBars := ssVertical; Memo.ReadOnly := True; Memo.Text := Content; Memo.Font.Name := 'Consolas'; Memo.Font.Size := 9; OKButton := TNewButton.Create(ResultForm); OKButton.Parent := ResultForm; OKButton.Caption := 'OK'; OKButton.Width := 80; OKButton.Height := 30; OKButton.Left := (ResultForm.ClientWidth - OKButton.Width) div 2; OKButton.Top := ResultForm.ClientHeight - 45; OKButton.ModalResult := mrOK; OKButton.Default := True; ResultForm.ActiveControl := OKButton; ResultForm.ShowModal; ResultForm.Free; end; // ============================================================================ // MENU OPERATIONS // ============================================================================ procedure DoViewMappings; var Mappings: String; begin Mappings := GetCurrentDriveMappings(False); if Mappings = '' then Mappings := 'No output received from PowerShell script.'; ShowResultsInMemo('Current Network Drive Mappings', Mappings); end; procedure DoBackupMappings; var Success: Boolean; BackupDate, MappingsList: String; begin // Check for existing backup if FileExists(BackupFilePath) then begin if LoadBackupInfo(BackupDate, MappingsList) then begin if MsgBox('Existing backup found from: ' + BackupDate + #13#10 + 'Do you want to overwrite it?', mbConfirmation, MB_YESNO) = IDNO then begin ShowResultsInMemo('Backup', 'Backup cancelled - keeping existing backup from ' + BackupDate); Exit; end; end; end; Success := BackupDriveMappings; if Success then ShowResultsInMemo('Backup Complete', 'Drive mappings saved to:' + #13#10 + BackupFilePath) else ShowResultsInMemo('Backup Failed', 'Could not save backup to:' + #13#10 + BackupFilePath + #13#10#13#10 + 'BackupFilePath: ' + BackupFilePath); end; procedure DoTestCredentials; var ErrorCode: Integer; ErrorMsg: String; Success: Boolean; Servers: TArrayOfString; I: Integer; Results: String; begin if CurrentPassword = '' then begin MsgBox('Please enter your password first.', mbError, MB_OK); Exit; end; ProgressPage.SetText('Testing credentials...', ''); ProgressPage.Show; try Servers := GetLegacyServers; Results := 'Testing credentials for: ' + LegacyUsername + NL + NL; for I := 0 to GetArrayLength(Servers) - 1 do begin ProgressPage.SetText('Testing ' + Servers[I] + '...', ''); ProgressPage.SetProgress((I + 1) * 100 div GetArrayLength(Servers), 100); Success := TestCredentials(Servers[I], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg); if Success then Results := Results + Servers[I] + ': OK' + NL else begin Results := Results + Servers[I] + ': FAILED' + NL; Results := Results + ' Error ' + IntToStr(ErrorCode) + ': ' + ErrorMsg + NL; // Handle specific errors if ErrorCode = 1219 then begin Results := Results + NL + 'Clearing existing connections...' + NL; Results := Results + ClearAllLegacyConnections + NL; Results := Results + NL + 'Please try testing again.' + NL; end else if (ErrorCode = 86) or (ErrorCode = 1326) or (ErrorCode = 1330) then begin Results := Results + NL + 'Your password may need to be reset.' + NL; Results := Results + 'Use option 6 to open the password reset portal.' + NL; end else if ErrorCode = 53 then begin Results := Results + NL + 'Please re-authenticate to Zscaler and try again.' + NL; end; end; end; ShowResultsInMemo('Credential Test Results', Results); finally ProgressPage.Hide; end; end; procedure DoFullReset; var ErrorCode: Integer; ErrorMsg: String; Success: Boolean; Servers: TArrayOfString; Results: String; begin if CurrentPassword = '' then begin MsgBox('Please enter your password first.', mbError, MB_OK); Exit; end; if MsgBox('This will:' + NL + '1. Backup your current drive mappings' + NL + '2. Test your credentials' + NL + '3. Disconnect all legacy drives' + NL + '4. Clear cached credentials' + NL + '5. Reconnect drives with new credentials' + NL + 'Continue?', mbConfirmation, MB_YESNO) = IDNO then Exit; ProgressPage.Show; Results := ''; try // Step 1: Backup ProgressPage.SetText('Step 1/5: Backing up current mappings...', ''); ProgressPage.SetProgress(10, 100); if BackupDriveMappings then Results := Results + '[OK] Backup saved to ' + BackupFilePath + NL else Results := Results + '[WARN] Backup failed - continuing anyway' + NL; // Step 2: Test credentials ProgressPage.SetText('Step 2/5: Testing credentials...', ''); ProgressPage.SetProgress(30, 100); Servers := GetLegacyServers; Success := TestCredentials(Servers[0], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg); if not Success then begin if ErrorCode = 1219 then begin Results := Results + '[INFO] Clearing existing connections first...' + NL; ClearAllLegacyConnections; // Retry Success := TestCredentials(Servers[0], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg); end; end; if Success then Results := Results + '[OK] Credentials validated' + NL else begin Results := Results + '[FAILED] Credential test failed: ' + ErrorMsg + NL; Results := Results + NL + 'Please fix the credential issue before continuing.' + NL; ShowResultsInMemo('Full Reset - Aborted', Results); Exit; end; // Step 3: Clear connections ProgressPage.SetText('Step 3/5: Clearing existing connections...', ''); ProgressPage.SetProgress(50, 100); Results := Results + NL + ClearAllLegacyConnections + NL; // Step 4: Wait a moment ProgressPage.SetText('Step 4/5: Waiting for connections to clear...', ''); ProgressPage.SetProgress(70, 100); Sleep(2000); // Step 5: Reconnect ProgressPage.SetText('Step 5/5: Reconnecting drives...', ''); ProgressPage.SetProgress(90, 100); Results := Results + NL + 'Reconnecting drives:' + NL; Results := Results + ReconnectDrivesFromBackup(LegacyUsername, CurrentPassword); ProgressPage.SetProgress(100, 100); Results := Results + NL + '=== Full Reset Complete ===' + NL; ShowResultsInMemo('Full Reset Results', Results); finally ProgressPage.Hide; end; end; procedure DoRestoreFromBackup; var BackupDate, MappingsList: String; Results: String; begin if CurrentPassword = '' then begin MsgBox('Please enter your password first.', mbError, MB_OK); Exit; end; if not FileExists(BackupFilePath) then begin MsgBox('No backup file found at:' + NL + BackupFilePath, mbError, MB_OK); Exit; end; if not LoadBackupInfo(BackupDate, MappingsList) then begin MsgBox('Could not read backup file.', mbError, MB_OK); Exit; end; if MsgBox('Restore drives from backup dated: ' + BackupDate + '?', mbConfirmation, MB_YESNO) = IDNO then Exit; ProgressPage.SetText('Restoring drives from backup...', ''); ProgressPage.SetProgress(50, 100); ProgressPage.Show; try Results := 'Restoring from backup dated: ' + BackupDate + NL + NL; Results := Results + ReconnectDrivesFromBackup(LegacyUsername, CurrentPassword); ShowResultsInMemo('Restore Results', Results); finally ProgressPage.Hide; end; end; procedure DoAddDrives; var DriveForm: TSetupForm; InfoLabel: TLabel; ChkDriveS, ChkDriveV, ChkDriveY: TNewCheckBox; OKButton, CancelButton: TNewButton; Results: String; Script: String; Output: String; AddS, AddV, AddY: Boolean; begin if CurrentPassword = '' then begin MsgBox('Please enter your password first.', mbError, MB_OK); Exit; end; // Create drive selection dialog DriveForm := CreateCustomForm; DriveForm.Caption := 'Add Network Drives'; DriveForm.ClientWidth := 450; DriveForm.ClientHeight := 280; DriveForm.Position := poScreenCenter; InfoLabel := TLabel.Create(DriveForm); InfoLabel.Parent := DriveForm; InfoLabel.Left := 20; InfoLabel.Top := 20; InfoLabel.Width := 410; InfoLabel.Height := 40; InfoLabel.AutoSize := False; InfoLabel.WordWrap := True; InfoLabel.Caption := 'Select the network drives you want to add. Existing drives with the same letter will be replaced.'; ChkDriveS := TNewCheckBox.Create(DriveForm); ChkDriveS.Parent := DriveForm; ChkDriveS.Left := 20; ChkDriveS.Top := 70; ChkDriveS.Width := 410; ChkDriveS.Height := 20; ChkDriveS.Caption := DRIVE_S_DESC; ChkDriveS.Checked := True; ChkDriveV := TNewCheckBox.Create(DriveForm); ChkDriveV.Parent := DriveForm; ChkDriveV.Left := 20; ChkDriveV.Top := 100; ChkDriveV.Width := 410; ChkDriveV.Height := 20; ChkDriveV.Caption := DRIVE_V_DESC; ChkDriveV.Checked := False; ChkDriveY := TNewCheckBox.Create(DriveForm); ChkDriveY.Parent := DriveForm; ChkDriveY.Left := 20; ChkDriveY.Top := 130; ChkDriveY.Width := 410; ChkDriveY.Height := 20; ChkDriveY.Caption := DRIVE_Y_DESC; ChkDriveY.Checked := False; OKButton := TNewButton.Create(DriveForm); OKButton.Parent := DriveForm; OKButton.Caption := 'Add Selected Drives'; OKButton.Width := 130; OKButton.Height := 30; OKButton.Left := 100; OKButton.Top := DriveForm.ClientHeight - 50; OKButton.ModalResult := mrOK; OKButton.Default := True; CancelButton := TNewButton.Create(DriveForm); CancelButton.Parent := DriveForm; CancelButton.Caption := 'Cancel'; CancelButton.Width := 80; CancelButton.Height := 30; CancelButton.Left := 250; CancelButton.Top := DriveForm.ClientHeight - 50; CancelButton.ModalResult := mrCancel; if DriveForm.ShowModal = mrOK then begin AddS := ChkDriveS.Checked; AddV := ChkDriveV.Checked; AddY := ChkDriveY.Checked; DriveForm.Free; if not (AddS or AddV or AddY) then begin ShowResultsInMemo('Add Drives', 'No drives selected.'); Exit; end; Results := 'Adding network drives...' + #13#10#13#10; // Build PowerShell script using three-phase approach to avoid error 1219 // when multiple drives point to the same server (e.g., S: and V: both on tsgwp00525) Script := '$Username = "' + LegacyUsername + '"' + NL + '$Password = "' + CurrentPassword + '"' + NL + '$results = @()' + NL + '$ManagedServers = @("' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + NL + '# Collect drives to add' + NL + '$drivesToAdd = @()' + NL; if AddS then Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "S:"; Path = "' + DRIVE_S_PATH + '" }' + NL; if AddV then Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "V:"; Path = "' + DRIVE_V_PATH + '" }' + NL; if AddY then Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "Y:"; Path = "' + DRIVE_Y_PATH + '" }' + NL; Script := Script + NL + '# PHASE 1: Disconnect ALL selected drives first' + NL + '$results += "Phase 1: Disconnecting existing drives..."' + NL + 'foreach ($drive in $drivesToAdd) {' + NL + ' $null = net use $drive.Letter /delete /y 2>&1' + NL + '}' + NL + NL + '# PHASE 2: Clear IPC$ connections to prevent error 1219' + NL + '$results += "Phase 2: Clearing server connections..."' + NL + 'foreach ($server in $ManagedServers) {' + NL + ' $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL + ' $null = net use "\\$server" /delete /y 2>&1' + NL + ' $null = cmdkey /delete:$server 2>&1' + NL + '}' + NL + NL + '# Small delay to let Windows release connections' + NL + 'Start-Sleep -Seconds 2' + NL + NL + '# PHASE 3: Reconnect ALL drives' + NL + '$results += "Phase 3: Connecting drives..."' + NL + '$results += ""' + NL + 'foreach ($drive in $drivesToAdd) {' + NL + ' $output = net use $drive.Letter $drive.Path /user:$Username $Password /persistent:yes 2>&1' + NL + ' if ($LASTEXITCODE -eq 0) {' + NL + ' $results += "OK: $($drive.Letter) -> $($drive.Path)"' + NL + ' } else {' + NL + ' $errCode = $LASTEXITCODE' + NL + ' $outStr = $output | Out-String' + NL + ' if ($outStr -match "System error (\d+)") { $errCode = [int]$Matches[1] }' + NL + ' $results += "FAILED: $($drive.Letter) -> $($drive.Path) (Error $errCode)"' + NL + ' }' + NL + '}' + NL + NL + '$results | ForEach-Object { Write-Output $_ }'; ExecPowerShell(Script, Output); Results := Results + Output; ShowResultsInMemo('Add Drives Results', Results); end else DriveForm.Free; end; procedure DoOpenPasswordPortal; var ErrorCode: Integer; begin ShellExec('open', PASSWORD_RESET_URL, '', '', SW_SHOW, ewNoWait, ErrorCode); ShowResultsInMemo('Password Portal', 'Browser opened to: ' + PASSWORD_RESET_URL + NL + 'After resetting your password, wait 10 minutes for' + NL + 'the change to sync to the legacy domain, then return' + NL + 'here and enter your new password.'); end; // ============================================================================ // MENU CLICK HANDLER // ============================================================================ procedure MenuListBoxClick(Sender: TObject); begin SelectedMenuOption := MenuListBox.ItemIndex + 1; end; procedure MenuListBoxDblClick(Sender: TObject); begin SelectedMenuOption := MenuListBox.ItemIndex + 1; WizardForm.NextButton.OnClick(WizardForm.NextButton); end; // ============================================================================ // PASSWORD PAGE HANDLING // ============================================================================ procedure PasswordPageOnChange(Sender: TObject); begin CurrentPassword := PasswordPage.Values[0]; end; // ============================================================================ // WIZARD PAGE CREATION // ============================================================================ procedure InitializeWizard; var MenuLabel: TLabel; PasswordLabel: TLabel; UsernameLabel: TLabel; begin // Initialize globals LegacyUsername := GetLegacyUsername; BackupFilePath := GetBackupFilePath; CurrentPassword := ''; SelectedMenuOption := 0; // ========================================= // Main Menu Page // ========================================= MainMenuPage := CreateCustomPage(wpWelcome, 'Network Drive Manager', 'Select an option from the menu below'); // Status/info label at top StatusLabel := TLabel.Create(MainMenuPage); StatusLabel.Parent := MainMenuPage.Surface; StatusLabel.Left := 0; StatusLabel.Top := 0; StatusLabel.Width := MainMenuPage.SurfaceWidth; StatusLabel.Height := 50; StatusLabel.AutoSize := False; StatusLabel.WordWrap := True; StatusLabel.Caption := 'User: ' + GetUserNameString + #13#10 + 'Legacy Account: ' + LegacyUsername + #13#10 + 'Backup: ' + BackupFilePath; // Menu label MenuLabel := TLabel.Create(MainMenuPage); MenuLabel.Parent := MainMenuPage.Surface; MenuLabel.Left := 0; MenuLabel.Top := 60; MenuLabel.Caption := 'Select an option:'; MenuLabel.Font.Style := [fsBold]; // Menu list box MenuListBox := TNewListBox.Create(MainMenuPage); MenuListBox.Parent := MainMenuPage.Surface; MenuListBox.Left := 0; MenuListBox.Top := 80; MenuListBox.Width := MainMenuPage.SurfaceWidth; MenuListBox.Height := 180; MenuListBox.Items.Add('1. View current network drive mappings'); MenuListBox.Items.Add('2. Backup current mappings'); MenuListBox.Items.Add('3. Test legacy credentials'); MenuListBox.Items.Add('4. Full reset - Disconnect, clear creds, and reconnect'); MenuListBox.Items.Add('5. Restore drives from backup'); MenuListBox.Items.Add('6. Add new network drives (S:, V:, Y:)'); MenuListBox.Items.Add('7. Open password reset portal'); MenuListBox.Items.Add('8. Quit'); MenuListBox.ItemIndex := 0; MenuListBox.OnClick := @MenuListBoxClick; MenuListBox.OnDblClick := @MenuListBoxDblClick; // ========================================= // Password Page // ========================================= PasswordPage := CreateInputQueryPage(MainMenuPage.ID, 'Enter Legacy Password', 'Enter your legacy domain password to continue', 'This password will be used to test credentials and reconnect drives.' + #13#10 + 'Your password is not stored permanently.'); PasswordPage.Add('Legacy Password:', True); PasswordPage.Edits[0].OnChange := @PasswordPageOnChange; // Add username display UsernameLabel := TLabel.Create(PasswordPage); UsernameLabel.Parent := PasswordPage.Surface; UsernameLabel.Left := 0; UsernameLabel.Top := 0; UsernameLabel.Caption := 'Username: ' + LegacyUsername; UsernameLabel.Font.Style := [fsBold]; // ========================================= // Progress Page // ========================================= ProgressPage := CreateOutputProgressPage('Working...', 'Please wait while the operation completes.'); // ========================================= // Results Page (comes after password page) // ========================================= ResultsPage := CreateOutputMsgMemoPage(PasswordPage.ID, 'Results', 'Operation complete', 'Results:', ''); ResultsPage.RichEditViewer.ScrollBars := ssVertical; // ========================================= // Auto-backup on startup // ========================================= if FileExists(BackupFilePath) then begin // Backup exists - will ask on first backup operation end else begin // No backup - create one automatically if BackupDriveMappings then StatusLabel.Caption := StatusLabel.Caption + NL + '[Auto-backup created]'; end; end; // ============================================================================ // PAGE NAVIGATION // ============================================================================ function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; // Skip password page for operations that don't need it if PageID = PasswordPage.ID then begin case SelectedMenuOption of MENU_VIEW_MAPPINGS, MENU_BACKUP, MENU_PASSWORD_PORTAL, MENU_QUIT: Result := True; // These require password: MENU_TEST_CREDS, MENU_FULL_RESET, MENU_RESTORE, MENU_ADD_DRIVES end; end; // Always skip results page - we use modal dialogs now if PageID = ResultsPage.ID then Result := True; end; function NextButtonClick(CurPageID: Integer): Boolean; begin Result := True; if CurPageID = MainMenuPage.ID then begin SelectedMenuOption := MenuListBox.ItemIndex + 1; // Handle quit immediately if SelectedMenuOption = MENU_QUIT then begin WizardForm.Close; Result := False; Exit; end; // Handle operations that don't need password - stay on main menu case SelectedMenuOption of MENU_VIEW_MAPPINGS: begin DoViewMappings; Result := False; // Stay on main menu end; MENU_BACKUP: begin DoBackupMappings; Result := False; // Stay on main menu end; MENU_PASSWORD_PORTAL: begin DoOpenPasswordPortal; Result := False; // Stay on main menu end; end; // For other options (3,4,5), proceed to password page end else if CurPageID = PasswordPage.ID then begin // Validate password entered if CurrentPassword = '' then begin MsgBox('Please enter your password.', mbError, MB_OK); Result := False; Exit; end; // Execute the selected operation case SelectedMenuOption of MENU_TEST_CREDS: DoTestCredentials; MENU_FULL_RESET: DoFullReset; MENU_RESTORE: DoRestoreFromBackup; MENU_ADD_DRIVES: DoAddDrives; end; // Clear password and go back to main menu CurrentPassword := ''; PasswordPage.Values[0] := ''; Result := False; // Don't proceed forward // Navigate back to main menu WizardForm.BackButton.OnClick(WizardForm.BackButton); end; end; procedure CurPageChanged(CurPageID: Integer); begin // Customize button text based on page if CurPageID = MainMenuPage.ID then begin WizardForm.NextButton.Caption := 'Select'; WizardForm.BackButton.Visible := False; WizardForm.CancelButton.Caption := 'Quit'; end else if CurPageID = PasswordPage.ID then begin WizardForm.NextButton.Caption := 'Continue'; WizardForm.BackButton.Caption := 'Back'; WizardForm.BackButton.Visible := True; end; end;