Files
poshmanager/server/services/psadtMigrationService.js
2026-06-25 12:05:53 -05:00

149 lines
7.6 KiB
JavaScript

const replacements = [
{ id: 'MIG-FN-EXECUTE-PROCESS', label: 'Execute-Process to Start-ADTProcess', pattern: /\bExecute-Process\b/g, replacement: 'Start-ADTProcess', automatic: true },
{ id: 'MIG-FN-EXECUTE-MSI', label: 'Execute-MSI to Start-ADTMsiProcess', pattern: /\bExecute-MSI\b/g, replacement: 'Start-ADTMsiProcess', automatic: true },
{ id: 'MIG-FN-EXECUTE-MSP', label: 'Execute-MSP to Start-ADTMspProcess', pattern: /\bExecute-MSP\b/g, replacement: 'Start-ADTMspProcess', automatic: true },
{ id: 'MIG-FN-WELCOME', label: 'Show-InstallationWelcome to Show-ADTInstallationWelcome', pattern: /\bShow-InstallationWelcome\b/g, replacement: 'Show-ADTInstallationWelcome', automatic: true },
{ id: 'MIG-FN-PROMPT', label: 'Show-InstallationPrompt to Show-ADTInstallationPrompt', pattern: /\bShow-InstallationPrompt\b/g, replacement: 'Show-ADTInstallationPrompt', automatic: true },
{ id: 'MIG-FN-PROGRESS', label: 'Show-InstallationProgress to Show-ADTInstallationProgress', pattern: /\bShow-InstallationProgress\b/g, replacement: 'Show-ADTInstallationProgress', automatic: true },
{ id: 'MIG-FN-RESTART', label: 'Show-InstallationRestartPrompt to Show-ADTInstallationRestartPrompt', pattern: /\bShow-InstallationRestartPrompt\b/g, replacement: 'Show-ADTInstallationRestartPrompt', automatic: true },
{ id: 'MIG-FN-LOG', label: 'Write-Log to Write-ADTLogEntry', pattern: /\bWrite-Log\b/g, replacement: 'Write-ADTLogEntry', automatic: true },
{ id: 'MIG-FN-REMOVE-MSI', label: 'Remove-MSIApplications to Uninstall-ADTApplication', pattern: /\bRemove-MSIApplications\b/g, replacement: 'Uninstall-ADTApplication', automatic: true },
{ id: 'MIG-FN-HKCU', label: 'Invoke-HKCURegistrySettingsForAllUsers to Invoke-ADTAllUsersRegistryAction', pattern: /\bInvoke-HKCURegistrySettingsForAllUsers\b/g, replacement: 'Invoke-ADTAllUsersRegistryAction', automatic: true },
{ id: 'MIG-NAME-DEPLOY-PS1', label: 'Deploy-Application.ps1 to Invoke-AppDeployToolkit.ps1', pattern: /\bDeploy-Application\.ps1\b/g, replacement: 'Invoke-AppDeployToolkit.ps1', automatic: true },
{ id: 'MIG-NAME-DEPLOY-EXE', label: 'Deploy-Application.exe to Invoke-AppDeployToolkit.exe', pattern: /\bDeploy-Application\.exe\b/g, replacement: 'Invoke-AppDeployToolkit.exe', automatic: true },
{ id: 'MIG-VAR-APPNAME', label: '$appName to $adtSession.AppName', pattern: /\$appName\b/g, replacement: '$adtSession.AppName', automatic: true },
{ id: 'MIG-VAR-APPVERSION', label: '$appVersion to $adtSession.AppVersion', pattern: /\$appVersion\b/g, replacement: '$adtSession.AppVersion', automatic: true },
{ id: 'MIG-VAR-APPVENDOR', label: '$appVendor to $adtSession.AppVendor', pattern: /\$appVendor\b/g, replacement: '$adtSession.AppVendor', automatic: true },
{ id: 'MIG-VAR-APPARCH', label: '$appArch to $adtSession.AppArch', pattern: /\$appArch\b/g, replacement: '$adtSession.AppArch', automatic: true },
{ id: 'MIG-VAR-APPLANG', label: '$appLang to $adtSession.AppLang', pattern: /\$appLang\b/g, replacement: '$adtSession.AppLang', automatic: true },
{ id: 'MIG-VAR-APPREVISION', label: '$appRevision to $adtSession.AppRevision', pattern: /\$appRevision\b/g, replacement: '$adtSession.AppRevision', automatic: true },
{ id: 'MIG-MODULE-420', label: 'Pin PSAppDeployToolkit ModuleVersion to 4.2.0', pattern: /ModuleVersion\s*=\s*['"][0-9.]+['"]/g, replacement: "ModuleVersion = '4.2.0'", automatic: true }
];
const manualChecks = [
{
id: 'MANUAL-SERVICEUI',
severity: 'critical',
pattern: /\b(ServiceUI\.exe|Invoke-ServiceUI\.ps1)\b/i,
title: 'Remove ServiceUI launch wrappers',
guidance: 'PSADT v4.1+ provides native user-session UI for Intune. Remove ServiceUI and test with SYSTEM context launch helpers.'
},
{
id: 'MANUAL-TEMPLATE-SHAPE',
severity: 'warning',
pattern: /function\s+(Install|Uninstall|Repair)-ADTDeployment/i,
title: 'Older function-based deployment phase shape detected',
guidance: 'Review the migrated script against the upstream v4 template that uses New-Variable phase scriptblocks and Open-ADTSession.'
},
{
id: 'MANUAL-RAW-MSIEXEC',
severity: 'warning',
pattern: /\bmsiexec(?:\.exe)?\b/i,
title: 'Raw msiexec call needs installer review',
guidance: 'Prefer Start-ADTMsiProcess or Start-ADTMspProcess so logging, success codes, and reboot handling stay consistent.'
},
{
id: 'MANUAL-INTUNE-DEFER',
severity: 'warning',
pattern: /Show-ADTInstallationWelcome[^\n\r]*-AllowDefer\b(?![^\n\r]*-DeferRunInterval)/i,
title: 'Intune deferral flow should include DeferRunInterval',
guidance: 'Add -DeferRunInterval to avoid repeated Intune retry prompts after a 1602 defer result.'
}
];
export function planPsadtMigration(content = '', options = {}) {
const source = String(content || '');
const steps = [];
let automaticCount = 0;
for (const rule of replacements) {
const matches = [...source.matchAll(rule.pattern)].filter((match) => !isCommentLine(source, match.index || 0));
if (!matches.length) continue;
automaticCount += matches.length;
steps.push({
id: rule.id,
type: 'replace',
automatic: rule.automatic,
status: 'ready',
title: rule.label,
count: matches.length,
lines: matches.slice(0, 8).map((match) => lineNumber(source, match.index || 0)),
replacement: rule.replacement
});
}
for (const rule of manualChecks) {
const match = rule.pattern.exec(source);
if (!match || isCommentLine(source, match.index || 0)) continue;
steps.push({
id: rule.id,
type: 'manual-review',
automatic: false,
status: 'review',
severity: rule.severity,
title: rule.title,
guidance: rule.guidance,
lines: [lineNumber(source, match.index || 0)]
});
}
if (!/Open-ADTSession/i.test(source)) {
steps.push({
id: 'MANUAL-OPEN-ADTSESSION',
type: 'manual-review',
automatic: false,
status: 'review',
severity: 'warning',
title: 'Open-ADTSession not detected',
guidance: 'Latest PSADT scripts should initialize the module and open an ADTSession before invoking phase logic.',
lines: []
});
}
return {
targetVersion: options.targetVersion || '4.2.0',
summary: {
steps: steps.length,
automatic: steps.filter((step) => step.automatic).length,
manual: steps.filter((step) => !step.automatic).length,
replacements: automaticCount
},
steps,
migratedContent: applyPsadtMigration(source)
};
}
export function applyPsadtMigration(content = '') {
let migrated = String(content || '');
for (const rule of replacements) {
migrated = replaceOutsideComments(migrated, rule.pattern, rule.replacement);
}
migrated = migrated.replace(/(Show-ADTInstallationPrompt[^\n\r]*)\s-Icon\s+\S+/gi, '$1');
if (!/POSHManager PSADT migration notes/i.test(migrated)) {
migrated = [
'# POSHManager PSADT migration notes:',
'# - Automatic replacements were applied for known v3/v4 legacy names and variables.',
'# - Review manual migration findings before deploying through Intune or any production channel.',
'',
migrated
].join('\n');
}
return migrated;
}
function replaceOutsideComments(content, pattern, replacement) {
return content.split(/\r?\n/).map((line) => {
if (line.trimStart().startsWith('#')) return line;
return line.replace(pattern, replacement);
}).join('\n');
}
function isCommentLine(content, index) {
const line = content.slice(0, index).split(/\r?\n/).length;
return content.split(/\r?\n/)[line - 1]?.trimStart().startsWith('#');
}
function lineNumber(content, index) {
return content.slice(0, index).split(/\r?\n/).length;
}