Initial
This commit is contained in:
148
server/services/psadtMigrationService.js
Normal file
148
server/services/psadtMigrationService.js
Normal file
@@ -0,0 +1,148 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user