Add FQDNUpdate drive remapping tool and standardize log paths
Add FQDNUpdate project for SSO drive remapping — scans for drives mapped to legacy server names (tsgwp00525, avwesj-gwy01), backs them up, clears stale credentials (cmdkey, IPC$, Kerberos), and remaps using canonical FQDNs with Windows SSO (no password). Includes .iss (pure Pascal Script), .ps1, and .bat launcher. Standardize all project log/backup paths to Documents\wjdt\logs\ with auto-creation of the directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
441
FQDNUpdate/FQDNUpdate.iss
Normal file
441
FQDNUpdate/FQDNUpdate.iss
Normal file
@@ -0,0 +1,441 @@
|
||||
; ============================================================================
|
||||
; FQDNUpdate - Drive Remapping Tool
|
||||
; Scans for drives mapped to legacy server names, backs them up,
|
||||
; clears stale credentials, and remaps using canonical FQDNs with SSO.
|
||||
; Pure Pascal Script — no PowerShell dependency.
|
||||
; ============================================================================
|
||||
|
||||
[Setup]
|
||||
AppId={{B1A2C3D4-E5F6-7890-ABCD-EF1234567890}}
|
||||
AppName=FQDNUpdate
|
||||
AppVersion=1.0
|
||||
AppPublisher=WJDT
|
||||
DefaultDirName={tmp}\FQDNUpdate
|
||||
CreateAppDir=no
|
||||
PrivilegesRequired=lowest
|
||||
OutputDir=Output
|
||||
OutputBaseFilename=FQDNUpdate
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
SetupIconFile=gea-logo.ico
|
||||
WizardImageFile=patrick.bmp
|
||||
WizardSmallImageFile=patrick-sm.bmp
|
||||
DisableWelcomePage=no
|
||||
DisableDirPage=yes
|
||||
DisableProgramGroupPage=yes
|
||||
DisableReadyPage=yes
|
||||
Uninstallable=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Messages]
|
||||
WelcomeLabel1=FQDNUpdate - Drive Remapping Tool
|
||||
WelcomeLabel2=This tool will scan your mapped network drives and remap any that point to legacy server names so they use Windows SSO (no password prompt).%n%nWhat it does:%n 1. Scans for drives mapped to tsgwp00525 or avwesj-gwy01%n 2. Backs up your current mappings%n 3. Disconnects and clears stale credentials%n 4. Remaps drives using the canonical server name%n%nNo password will be required — your Windows login is used automatically.%n%nClick Next to begin.
|
||||
FinishedHeadingLabel=Remapping Complete
|
||||
|
||||
[Code]
|
||||
// ============================================================================
|
||||
// CONSTANTS AND CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
const
|
||||
NL = #13#10;
|
||||
|
||||
// Server group 1
|
||||
OLD_SERVER_1 = 'tsgwp00525.rd.ds.ge.com';
|
||||
NEW_SERVER_1 = 'tsgwp00525.wjs.geaerospace.net';
|
||||
SHORT_NAME_1 = 'tsgwp00525';
|
||||
|
||||
// Server group 2
|
||||
OLD_SERVER_2 = 'avwesj-gwy01.av.ge.com';
|
||||
NEW_SERVER_2 = 'avwesj-gwy01.wjs.geaerospace.net';
|
||||
SHORT_NAME_2 = 'avwesj-gwy01';
|
||||
|
||||
SERVER_VARIANT_COUNT = 6;
|
||||
|
||||
// ============================================================================
|
||||
// GLOBAL VARIABLES
|
||||
// ============================================================================
|
||||
|
||||
var
|
||||
ProgressPage: TOutputProgressWizardPage;
|
||||
LogContent: String;
|
||||
FailureCount: Integer;
|
||||
BackupPath: String;
|
||||
LogPath: String;
|
||||
|
||||
// Parallel arrays for matched drives
|
||||
DriveLetters: TArrayOfString;
|
||||
RemotePaths: TArrayOfString;
|
||||
ShareNames: TArrayOfString;
|
||||
CanonicalFQDNs: TArrayOfString;
|
||||
DriveCount: Integer;
|
||||
|
||||
// ============================================================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
function ExecWithOutput(const Cmd, Params: 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',
|
||||
ExpandConstant('{sys}'), 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;
|
||||
|
||||
procedure Log(const Msg: String);
|
||||
begin
|
||||
LogContent := LogContent + Msg + NL;
|
||||
end;
|
||||
|
||||
function GetServerVariants: TArrayOfString;
|
||||
begin
|
||||
SetArrayLength(Result, SERVER_VARIANT_COUNT);
|
||||
Result[0] := OLD_SERVER_1;
|
||||
Result[1] := NEW_SERVER_1;
|
||||
Result[2] := SHORT_NAME_1;
|
||||
Result[3] := OLD_SERVER_2;
|
||||
Result[4] := NEW_SERVER_2;
|
||||
Result[5] := SHORT_NAME_2;
|
||||
end;
|
||||
|
||||
function GetCanonicalFQDN(const ServerName: String): String;
|
||||
var
|
||||
Lower: String;
|
||||
begin
|
||||
Lower := Lowercase(ServerName);
|
||||
if (Lower = Lowercase(OLD_SERVER_1)) or (Lower = Lowercase(NEW_SERVER_1)) or (Lower = Lowercase(SHORT_NAME_1)) then
|
||||
Result := NEW_SERVER_1
|
||||
else if (Lower = Lowercase(OLD_SERVER_2)) or (Lower = Lowercase(NEW_SERVER_2)) or (Lower = Lowercase(SHORT_NAME_2)) then
|
||||
Result := NEW_SERVER_2
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function ContainsServerVariant(const S: String): Boolean;
|
||||
var
|
||||
Lower: String;
|
||||
begin
|
||||
Lower := Lowercase(S);
|
||||
Result := (Pos(Lowercase(OLD_SERVER_1), Lower) > 0) or
|
||||
(Pos(Lowercase(NEW_SERVER_1), Lower) > 0) or
|
||||
(Pos(Lowercase(SHORT_NAME_1), Lower) > 0) or
|
||||
(Pos(Lowercase(OLD_SERVER_2), Lower) > 0) or
|
||||
(Pos(Lowercase(NEW_SERVER_2), Lower) > 0) or
|
||||
(Pos(Lowercase(SHORT_NAME_2), Lower) > 0);
|
||||
end;
|
||||
|
||||
function ExtractServerFromUNC(const UNCPath: String): String;
|
||||
var
|
||||
S: String;
|
||||
SlashPos: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
if (Length(UNCPath) > 2) and (UNCPath[1] = '\') and (UNCPath[2] = '\') then
|
||||
begin
|
||||
S := Copy(UNCPath, 3, Length(UNCPath) - 2);
|
||||
SlashPos := Pos('\', S);
|
||||
if SlashPos > 0 then
|
||||
Result := Copy(S, 1, SlashPos - 1)
|
||||
else
|
||||
Result := S;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ExtractShareFromUNC(const UNCPath: String): String;
|
||||
var
|
||||
S: String;
|
||||
SlashPos: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
if (Length(UNCPath) > 2) and (UNCPath[1] = '\') and (UNCPath[2] = '\') then
|
||||
begin
|
||||
S := Copy(UNCPath, 3, Length(UNCPath) - 2);
|
||||
SlashPos := Pos('\', S);
|
||||
if SlashPos > 0 then
|
||||
Result := Copy(S, SlashPos + 1, Length(S) - SlashPos);
|
||||
end;
|
||||
end;
|
||||
|
||||
function GetTimestamp: String;
|
||||
begin
|
||||
Result := GetDateTimeString('yyyymmdd_hhnnss', '-', '-');
|
||||
end;
|
||||
|
||||
function GetLogDir: String;
|
||||
begin
|
||||
Result := ExpandConstant('{userdocs}\wjdt\logs');
|
||||
ForceDirectories(Result);
|
||||
end;
|
||||
|
||||
// ============================================================================
|
||||
// PHASE 1: SCAN & BACKUP
|
||||
// ============================================================================
|
||||
|
||||
procedure ScanAndBackup;
|
||||
var
|
||||
NetOutput, Line, DriveLetter, RemotePath, ServerName, ShareName, Canonical: String;
|
||||
I, SpacePos, SecondSpace: Integer;
|
||||
BackupContent: String;
|
||||
begin
|
||||
Log('=== Phase 1: Scan & Backup ===');
|
||||
Log('');
|
||||
DriveCount := 0;
|
||||
SetArrayLength(DriveLetters, 26);
|
||||
SetArrayLength(RemotePaths, 26);
|
||||
SetArrayLength(ShareNames, 26);
|
||||
SetArrayLength(CanonicalFQDNs, 26);
|
||||
|
||||
ExecWithOutput('net', 'use', NetOutput);
|
||||
|
||||
NetOutput := NetOutput + NL;
|
||||
while Length(NetOutput) > 0 do
|
||||
begin
|
||||
I := Pos(#13#10, NetOutput);
|
||||
if I = 0 then I := Pos(#10, NetOutput);
|
||||
if I = 0 then
|
||||
begin
|
||||
Line := NetOutput;
|
||||
NetOutput := '';
|
||||
end
|
||||
else
|
||||
begin
|
||||
Line := Copy(NetOutput, 1, I - 1);
|
||||
if (I < Length(NetOutput)) and (NetOutput[I] = #13) then
|
||||
NetOutput := Copy(NetOutput, I + 2, Length(NetOutput))
|
||||
else
|
||||
NetOutput := Copy(NetOutput, I + 1, Length(NetOutput));
|
||||
end;
|
||||
|
||||
if not ContainsServerVariant(Line) then
|
||||
Continue;
|
||||
|
||||
DriveLetter := '';
|
||||
RemotePath := '';
|
||||
for I := 1 to Length(Line) - 1 do
|
||||
begin
|
||||
if (Line[I] >= 'A') and (Line[I] <= 'Z') and (Line[I+1] = ':') then
|
||||
begin
|
||||
DriveLetter := Line[I] + ':';
|
||||
SpacePos := I + 2;
|
||||
while (SpacePos <= Length(Line)) and (Line[SpacePos] = ' ') do
|
||||
SpacePos := SpacePos + 1;
|
||||
|
||||
if (SpacePos < Length(Line)) and (Line[SpacePos] = '\') and (Line[SpacePos+1] = '\') then
|
||||
begin
|
||||
SecondSpace := SpacePos;
|
||||
while (SecondSpace <= Length(Line)) and (Line[SecondSpace] <> ' ') do
|
||||
SecondSpace := SecondSpace + 1;
|
||||
RemotePath := Copy(Line, SpacePos, SecondSpace - SpacePos);
|
||||
end;
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (DriveLetter <> '') and (RemotePath <> '') then
|
||||
begin
|
||||
ServerName := ExtractServerFromUNC(RemotePath);
|
||||
ShareName := ExtractShareFromUNC(RemotePath);
|
||||
Canonical := GetCanonicalFQDN(ServerName);
|
||||
|
||||
if Canonical <> '' then
|
||||
begin
|
||||
DriveLetters[DriveCount] := DriveLetter;
|
||||
RemotePaths[DriveCount] := RemotePath;
|
||||
ShareNames[DriveCount] := ShareName;
|
||||
CanonicalFQDNs[DriveCount] := Canonical;
|
||||
DriveCount := DriveCount + 1;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
if DriveCount = 0 then
|
||||
begin
|
||||
Log('No drives found mapped to target servers.');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
Log('Found ' + IntToStr(DriveCount) + ' drive(s):');
|
||||
for I := 0 to DriveCount - 1 do
|
||||
Log(' ' + DriveLetters[I] + ' -> ' + RemotePaths[I]);
|
||||
|
||||
BackupContent := '';
|
||||
for I := 0 to DriveCount - 1 do
|
||||
BackupContent := BackupContent + DriveLetters[I] + '|' + RemotePaths[I] + NL;
|
||||
|
||||
BackupPath := GetLogDir + '\fqdnupdate_' + GetTimestamp + '.txt';
|
||||
SaveStringToFile(BackupPath, BackupContent, False);
|
||||
Log('');
|
||||
Log('Backup saved to ' + BackupPath);
|
||||
end;
|
||||
|
||||
// ============================================================================
|
||||
// PHASE 2: DISCONNECT & CLEAR
|
||||
// ============================================================================
|
||||
|
||||
procedure DisconnectAndClear;
|
||||
var
|
||||
Output: String;
|
||||
Variants: TArrayOfString;
|
||||
I: Integer;
|
||||
begin
|
||||
Log('');
|
||||
Log('=== Phase 2: Disconnect & Clear ===');
|
||||
Log('');
|
||||
Variants := GetServerVariants;
|
||||
|
||||
for I := 0 to DriveCount - 1 do
|
||||
begin
|
||||
ExecWithOutput('net', 'use ' + DriveLetters[I] + ' /delete /y', Output);
|
||||
Log(' Disconnected ' + DriveLetters[I]);
|
||||
end;
|
||||
|
||||
for I := 0 to GetArrayLength(Variants) - 1 do
|
||||
begin
|
||||
ExecWithOutput('net', 'use "\\' + Variants[I] + '\IPC$" /delete /y', Output);
|
||||
ExecWithOutput('net', 'use "\\' + Variants[I] + '" /delete /y', Output);
|
||||
end;
|
||||
Log(' Cleared IPC$/server connections');
|
||||
|
||||
for I := 0 to GetArrayLength(Variants) - 1 do
|
||||
begin
|
||||
ExecWithOutput('cmdkey', '/delete:' + Variants[I], Output);
|
||||
ExecWithOutput('cmdkey', '/delete:"Domain:target=' + Variants[I] + '"', Output);
|
||||
end;
|
||||
Log(' Cleared cached credentials');
|
||||
|
||||
ExecWithOutput('klist', 'purge', Output);
|
||||
Log(' Purged Kerberos ticket cache');
|
||||
|
||||
Sleep(2000);
|
||||
Log(' Waited 2 seconds for connections to release');
|
||||
end;
|
||||
|
||||
// ============================================================================
|
||||
// PHASE 3: REMAP WITH SSO
|
||||
// ============================================================================
|
||||
|
||||
procedure RemapDrives;
|
||||
var
|
||||
Output, NewPath: String;
|
||||
ExitCode, I: Integer;
|
||||
begin
|
||||
Log('');
|
||||
Log('=== Phase 3: Remap with SSO ===');
|
||||
Log('');
|
||||
FailureCount := 0;
|
||||
|
||||
for I := 0 to DriveCount - 1 do
|
||||
begin
|
||||
NewPath := '\\' + CanonicalFQDNs[I] + '\' + ShareNames[I];
|
||||
ExitCode := ExecWithOutput('net', 'use ' + DriveLetters[I] + ' "' + NewPath + '" /persistent:yes', Output);
|
||||
|
||||
if ExitCode = 0 then
|
||||
Log(' OK: ' + DriveLetters[I] + ' -> ' + NewPath)
|
||||
else
|
||||
begin
|
||||
Log(' FAILED: ' + DriveLetters[I] + ' -> ' + NewPath + ' (Error ' + IntToStr(ExitCode) + ')');
|
||||
FailureCount := FailureCount + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
Log('');
|
||||
Log('=== Complete ===');
|
||||
end;
|
||||
|
||||
// ============================================================================
|
||||
// WIZARD PAGE CREATION
|
||||
// ============================================================================
|
||||
|
||||
procedure InitializeWizard;
|
||||
begin
|
||||
ProgressPage := CreateOutputProgressPage('Remapping Drives', 'Please wait while your drives are remapped...');
|
||||
end;
|
||||
|
||||
// ============================================================================
|
||||
// PAGE NAVIGATION
|
||||
// ============================================================================
|
||||
|
||||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
|
||||
if CurPageID = wpWelcome then
|
||||
begin
|
||||
LogContent := '';
|
||||
FailureCount := 0;
|
||||
Log('FQDNUpdate Log - ' + GetDateTimeString('yyyy-mm-dd hh:nn:ss', '-', ':'));
|
||||
Log('User: ' + GetUserNameString + ' Computer: ' + ExpandConstant('{computername}'));
|
||||
Log('');
|
||||
|
||||
ProgressPage.Show;
|
||||
try
|
||||
// Phase 1: Scan & Backup
|
||||
ProgressPage.SetText('Scanning for target drives...', '');
|
||||
ProgressPage.SetProgress(10, 100);
|
||||
ScanAndBackup;
|
||||
|
||||
if DriveCount > 0 then
|
||||
begin
|
||||
// Phase 2: Disconnect & Clear
|
||||
ProgressPage.SetText('Disconnecting drives and clearing credentials...', '');
|
||||
ProgressPage.SetProgress(30, 100);
|
||||
DisconnectAndClear;
|
||||
|
||||
// Phase 3: Remap
|
||||
ProgressPage.SetText('Remapping drives with SSO...', '');
|
||||
ProgressPage.SetProgress(70, 100);
|
||||
RemapDrives;
|
||||
end;
|
||||
|
||||
ProgressPage.SetProgress(100, 100);
|
||||
finally
|
||||
ProgressPage.Hide;
|
||||
end;
|
||||
|
||||
// Save log
|
||||
LogPath := GetLogDir + '\fqdnupdate_' + GetTimestamp + '.log';
|
||||
SaveStringToFile(LogPath, LogContent, False);
|
||||
|
||||
// Update finished page text dynamically
|
||||
if DriveCount = 0 then
|
||||
WizardForm.FinishedLabel.Caption :=
|
||||
'No drives were found mapped to target servers.' + NL + NL +
|
||||
'Nothing was changed.' + NL + NL +
|
||||
'Log saved to:' + NL + LogPath
|
||||
else if FailureCount = 0 then
|
||||
WizardForm.FinishedLabel.Caption :=
|
||||
'All ' + IntToStr(DriveCount) + ' drive(s) were successfully remapped to use Windows SSO.' + NL + NL +
|
||||
'Backup: ' + BackupPath + NL +
|
||||
'Log: ' + LogPath
|
||||
else
|
||||
WizardForm.FinishedLabel.Caption :=
|
||||
IntToStr(DriveCount - FailureCount) + ' of ' + IntToStr(DriveCount) + ' drive(s) remapped successfully.' + NL +
|
||||
IntToStr(FailureCount) + ' drive(s) failed — check the log for details.' + NL + NL +
|
||||
'Backup: ' + BackupPath + NL +
|
||||
'Log: ' + LogPath;
|
||||
end;
|
||||
end;
|
||||
|
||||
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
|
||||
begin
|
||||
Result := '';
|
||||
end;
|
||||
Reference in New Issue
Block a user