Files
inno-installers/NetworkDriveManager/NetworkDriveManager.iss
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

1279 lines
40 KiB
Plaintext

; ============================================================================
; 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;
begin
Result := ExpandConstant('{userdocs}\') + 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;