Update summary.

This commit is contained in:
Vasyl Palamarchuk
2026-09-09 11:46:36 -07:00
parent 8b6a618b5d
commit b2f4d6310e
10 changed files with 672 additions and 102 deletions
+252 -17
View File
@@ -67,6 +67,8 @@ param(
[string]$SharedPortableLabel = 'PORTABLE_APPS',
[ValidateSet('DailyTech & Tuning', 'ODIS & XENTRY', 'PIWIS & ISTA')]
[string]$WorkstationProfile = 'DailyTech & Tuning',
[ValidateSet('Apply', 'Audit', 'Plan', 'Repair', 'HealthCheck', 'Backup', 'Restore', 'Inventory', 'UpdatePortable', 'CreateWorkspace')]
[string]$Mode = 'Apply',
[switch]$InstallOptionalApps,
[switch]$CreateSharedLinks = $true,
[switch]$SkipDownloads,
@@ -82,7 +84,16 @@ param(
[ValidateRange(30, 3600)][int]$DownloadTimeoutSeconds = 900,
[switch]$EnableTranscript,
[string]$ExecutionId,
[switch]$ResumeFromCheckpoint
[switch]$ResumeFromCheckpoint,
[string]$JobId,
[string]$Manufacturer,
[string]$Model,
[ValidateRange(1886, 2100)][int]$ModelYear = (Get-Date).Year,
[ValidateSet('ECU', 'TCU', 'EEPROM', 'ABS', 'Other')]
[string]$Module = 'ECU',
[string]$VIN,
[string]$Notes,
[string]$Technician
)
Set-StrictMode -Version Latest
@@ -112,6 +123,8 @@ $Configuration = @{
Shortcuts = Import-SetupConfiguration -Name 'shortcuts.json'
Profiles = Import-SetupConfiguration -Name 'workstation-profiles.json'
Automotive = Import-SetupConfiguration -Name 'automotive-resources.json'
WindowsSettings = Import-SetupConfiguration -Name 'windows-settings.json'
WorkspaceTemplates = Import-SetupConfiguration -Name 'workspace-templates.json'
}
function Get-ProfileCatalogItems {
@@ -131,6 +144,15 @@ if ($Profile.Count -ne 1) {
throw "Workstation profile '$WorkstationProfile' is not defined in Config\workstation-profiles.json."
}
$Profile = $Profile[0]
$EffectiveMode = $Mode
if ($HealthCheck) { $EffectiveMode = 'HealthCheck' }
elseif ($BackupConfiguration) { $EffectiveMode = 'Backup' }
elseif ($RestoreConfiguration) { $EffectiveMode = 'Restore' }
$windowsProfileKey = $Profile.Folder
if (-not $Configuration.WindowsSettings.Profiles.PSObject.Properties[$windowsProfileKey]) {
throw "Windows settings are not defined for profile '$windowsProfileKey'."
}
$DesiredWindowsSettings = $Configuration.WindowsSettings.Profiles.$windowsProfileKey
$ProfileDataRoot = "{0}\Workstations\{1}" -f $SharedDataRoot.TrimEnd('\'), $Profile.Folder
$ProfileConfigRoot = "{0}\Config\{1}" -f $SharedPortableRoot.TrimEnd('\'), $Profile.Folder
$ProfileInstallerRoot = Join-Path $SharedPortableRoot (Join-Path 'Install' $Profile.Folder)
@@ -174,7 +196,7 @@ $Events = [System.Collections.Generic.List[object]]::new()
$ExecutionState = [ordered]@{
ExecutionId = $ExecutionId
Profile = $WorkstationProfile
Mode = 'Apply'
Mode = $EffectiveMode
StartTime = $ScriptStartTime.ToString('o')
LastCheckpoint = $null
Status = 'InProgress'
@@ -442,6 +464,170 @@ function Test-Administrator {
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Invoke-PowerCfg {
param([Parameter(Mandatory)][string[]]$Arguments)
$process = Start-Process -FilePath 'powercfg.exe' -ArgumentList $Arguments -Wait -PassThru -NoNewWindow
if ($process.ExitCode -ne 0) {
throw "powercfg.exe $($Arguments -join ' ') failed with exit code $($process.ExitCode)."
}
}
function Get-WindowsConfigurationState {
$activeScheme = (& powercfg.exe /getactivescheme 2>$null) -join ' '
$powerSettings = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Power' -Name HibernateEnabled -ErrorAction SilentlyContinue
$hibernateValue = if ($null -eq $powerSettings -or -not $powerSettings.PSObject.Properties['HibernateEnabled']) { $null } else { $powerSettings.HibernateEnabled }
$fileSystem = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -ErrorAction SilentlyContinue
$explorer = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -ErrorAction SilentlyContinue
return [pscustomobject]@{
Time = Get-Date
ActivePowerScheme = $activeScheme
HibernateEnabled = $hibernateValue
UsbSelectiveSuspend = 'Inspect with powercfg /query'
MonitorTimeoutAC = 'Inspect with powercfg /query'
DiskTimeoutAC = 'Inspect with powercfg /query'
StandbyTimeoutAC = 'Inspect with powercfg /query'
ShowFileExtensions = if ($null -eq $explorer -or -not $explorer.PSObject.Properties['HideFileExt']) { $null } else { $explorer.HideFileExt -eq 0 }
LongPathsEnabled = if ($null -eq $fileSystem -or -not $fileSystem.PSObject.Properties['LongPathsEnabled']) { $null } else { $fileSystem.LongPathsEnabled -eq 1 }
}
}
function Get-WindowsConfigurationPlan {
$current = Get-WindowsConfigurationState
$desired = $DesiredWindowsSettings
return @(
[pscustomobject]@{ Setting='PowerPlan'; Current=$current.ActivePowerScheme; Desired=$desired.PowerPlan; Action='Set active scheme' }
[pscustomobject]@{ Setting='Hibernation'; Current=if ($current.HibernateEnabled -eq 1) { 'Enabled' } else { 'Disabled' }; Desired=$desired.Hibernation; Action='powercfg /hibernate' }
[pscustomobject]@{ Setting='USB selective suspend'; Current=$current.UsbSelectiveSuspend; Desired=$desired.UsbSelectiveSuspend; Action='Set AC/DC USB policy' }
[pscustomobject]@{ Setting='Monitor timeout AC'; Current=$current.MonitorTimeoutAC; Desired="$($desired.MonitorTimeoutACMinutes) minutes"; Action='powercfg /change' }
[pscustomobject]@{ Setting='System sleep AC'; Current=$current.StandbyTimeoutAC; Desired="$($desired.StandbyTimeoutACMinutes) minutes"; Action='powercfg /change' }
[pscustomobject]@{ Setting='Show file extensions'; Current=$current.ShowFileExtensions; Desired=[bool]$desired.ShowFileExtensions; Action='Explorer registry setting' }
[pscustomobject]@{ Setting='Long paths'; Current=$current.LongPathsEnabled; Desired=[bool]$desired.LongPathsEnabled; Action='FileSystem registry setting' }
)
}
function Set-WindowsProfileConfiguration {
$desired = $DesiredWindowsSettings
if ($desired.Hibernation -eq 'Disabled') { Invoke-PowerCfg -Arguments @('/hibernate', 'off') }
else { Invoke-PowerCfg -Arguments @('/hibernate', 'on') }
$usbValue = if ($desired.UsbSelectiveSuspend -eq 'Disabled') { '0' } else { '1' }
Invoke-PowerCfg -Arguments @('/setacvalueindex', 'SCHEME_CURRENT', 'SUB_USB', 'USBSELECTIVE', $usbValue)
Invoke-PowerCfg -Arguments @('/setdcvalueindex', 'SCHEME_CURRENT', 'SUB_USB', 'USBSELECTIVE', $usbValue)
Invoke-PowerCfg -Arguments @('/setactive', $desired.PowerPlanAlias)
Invoke-PowerCfg -Arguments @('/change', 'monitor-timeout-ac', [string]$desired.MonitorTimeoutACMinutes)
Invoke-PowerCfg -Arguments @('/change', 'disk-timeout-ac', [string]$desired.DiskTimeoutACMinutes)
Invoke-PowerCfg -Arguments @('/change', 'standby-timeout-ac', [string]$desired.StandbyTimeoutACMinutes)
$explorerPath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'
$hideFileExtensionValue = if ($desired.ShowFileExtensions) { 0 } else { 1 }
Set-ItemProperty -Path $explorerPath -Name HideFileExt -Value $hideFileExtensionValue
$fileSystemPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem'
$longPathValue = if ($desired.LongPathsEnabled) { 1 } else { 0 }
Set-ItemProperty -Path $fileSystemPath -Name LongPathsEnabled -Value $longPathValue
Write-Log "Applied Windows settings for $($Profile.Folder): $($desired.PowerPlan), hibernation $($desired.Hibernation), USB suspend $($desired.UsbSelectiveSuspend)." 'OK'
}
function ConvertTo-ProjectSegment {
param([Parameter(Mandatory)][string]$Value, [Parameter(Mandatory)][string]$Name)
$segment = $Value.Trim()
if ([string]::IsNullOrWhiteSpace($segment) -or $segment -match '[\\/:*?"<>|]' -or $segment -eq '.' -or $segment -eq '..') {
throw "$Name contains an invalid path segment: '$Value'."
}
return ($segment -replace '\s+', '_')
}
function New-AutomotiveProject {
param(
[Parameter(Mandatory)][string]$ProjectJobId,
[Parameter(Mandatory)][string]$ProjectManufacturer,
[Parameter(Mandatory)][string]$ProjectModel,
[int]$ProjectModelYear = (Get-Date).Year,
[string]$ProjectVIN = '',
[string]$ProjectNotes = '',
[string]$ProjectTechnician = ''
)
$templateProperty = $Configuration.WorkspaceTemplates.Templates.PSObject.Properties[$Profile.Folder]
if (-not $templateProperty) { throw "Workspace template is not defined for profile '$($Profile.Folder)'." }
$template = $templateProperty.Value
$safeJobId = ConvertTo-ProjectSegment -Value $ProjectJobId -Name 'JobId'
$safeManufacturer = ConvertTo-ProjectSegment -Value $ProjectManufacturer -Name 'Manufacturer'
$safeModel = ConvertTo-ProjectSegment -Value $ProjectModel -Name 'Model'
$date = Get-Date -Format 'yyyy-MM-dd'
$projectName = '{0}_{1}_{2}_{3}' -f $safeJobId, $safeManufacturer, $safeModel, $date
$localBase = Join-Path $LocalRoot $template.LocalBase
$localProject = Join-Path $localBase $projectName
$sharedProject = Join-Path $ProfileDataRoot $projectName
$localFolders = @($template.Folders | ForEach-Object { Join-Path $localProject $_ })
$sharedFolders = @($template.Folders | ForEach-Object { Join-Path $sharedProject $_ })
$null = New-Item -ItemType Directory -Path @($localProject, $sharedProject) -Force
$null = New-Item -ItemType Directory -Path ($localFolders + $sharedFolders) -Force
$now = (Get-Date).ToString('o')
$metadata = [ordered]@{
SchemaVersion = '1.0'
JobId = $safeJobId
Profile = $Profile.Folder
CreatedDate = $now
LastModified = $now
Vehicle = [ordered]@{
Manufacturer = $safeManufacturer
Model = $safeModel
ModelYear = $ProjectModelYear
VIN = $ProjectVIN
}
Module = [ordered]@{ Type = $Module; PartNumber = ''; HardwareVersion = ''; SoftwareVersion = '' }
Work = [ordered]@{
OperationType = $template.OperationType
Technician = $ProjectTechnician
TechnicianNotes = $ProjectNotes
AuthorizationReference = ''
ToolsUsed = @()
}
Paths = [ordered]@{ Local = $localProject; Shared = $sharedProject }
Status = 'New'
}
$metadataPath = Join-Path $localProject 'ProjectMetadata.json'
$metadata | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $metadataPath -Encoding UTF8
$metadata | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $sharedProject 'ProjectMetadata.json') -Encoding UTF8
$jobLog = @"
# $safeJobId - $safeManufacturer $safeModel
**Profile:** $($Profile.Folder)
**Created:** $now
**Module:** $Module
**Technician:** $ProjectTechnician
**VIN:** $ProjectVIN
## Work Notes
$ProjectNotes
## Timeline
### $now - Project initialized
- Workspace folders created from the $($Profile.Folder) template.
- Original vehicle/module files belong in the `Originals` or profile-equivalent folder.
- Preserve originals and record SHA256 hashes before making changes.
## Recovery
- Local project: `$localProject`
- Shared project copy: `$sharedProject`
- Do not flash, code, erase, or modify a vehicle without explicit authorization and verified backups.
"@
$jobLog | Set-Content -LiteralPath (Join-Path $localProject 'JobLog.md') -Encoding UTF8
$jobLog | Set-Content -LiteralPath (Join-Path $sharedProject 'JobLog.md') -Encoding UTF8
return [pscustomobject]@{
JobId = $safeJobId
Profile = $Profile.Folder
LocalPath = $localProject
SharedPath = $sharedProject
MetadataPath = $metadataPath
}
}
function New-Shortcut {
param(
[Parameter(Mandatory)][string]$ShortcutPath,
@@ -835,6 +1021,7 @@ Write-Log "Local root: $LocalRoot"
Write-Log "Shared data root: $SharedDataRoot"
Write-Log "Shared portable root: $SharedPortableRoot"
Write-Log "Execution ID: $ExecutionId"
Write-Log "Execution mode: $EffectiveMode"
if ($ResumeFromCheckpoint) {
$checkpoint = Load-ExecutionCheckpoint
@@ -849,33 +1036,81 @@ if ($EnableTranscript) {
Start-Transcript -LiteralPath $transcriptPath -Append | Out-Null
}
if ($HealthCheck) {
if ($EffectiveMode -eq 'Audit' -or $EffectiveMode -eq 'HealthCheck') {
Invoke-Safe 'HealthCheck' 'Automotive workstation health check' { Test-WorkstationHealth | Out-Null } | Out-Null
if ($EffectiveMode -eq 'Audit') {
Invoke-Safe 'Inventory' 'Export workstation inventory' { Export-WorkstationInventory } | Out-Null
}
$Results | Export-Csv -LiteralPath $SummaryFile -NoTypeInformation -Encoding UTF8
if ($EnableTranscript) { Stop-Transcript | Out-Null }
return
}
if ($EffectiveMode -eq 'Plan') {
$windowsPlan = @(Get-WindowsConfigurationPlan)
$planFile = Join-Path $Paths.Logs ("{0}_Plan_{1}.csv" -f $Profile.Folder, $TimeStamp)
$windowsPlan | Export-Csv -LiteralPath $planFile -NoTypeInformation -Encoding UTF8
$catalogPlan = if ($Configuration.AppCatalog) {
@($Configuration.AppCatalog.Packages | Where-Object {
(-not $_.PSObject.Properties['Profiles']) -or @($_.Profiles) -contains $Profile.Folder
} | Select-Object Name, Id, Channel)
} else {
@($Configuration.CorePackages | Select-Object Name, Id | ForEach-Object { $_ | Add-Member -NotePropertyName Channel -NotePropertyValue 'Core' -PassThru })
}
$catalogPlan | Export-Csv -LiteralPath (Join-Path $Paths.Logs ("{0}_ApplicationPlan_{1}.csv" -f $Profile.Folder, $TimeStamp)) -NoTypeInformation -Encoding UTF8
Write-Log "Plan generated for $($Profile.Folder): $($catalogPlan.Count) catalog applications and $($windowsPlan.Count) Windows settings." 'OK'
$Results | Export-Csv -LiteralPath $SummaryFile -NoTypeInformation -Encoding UTF8
if ($EnableTranscript) { Stop-Transcript | Out-Null }
return
}
if ($EffectiveMode -eq 'CreateWorkspace') {
if ([string]::IsNullOrWhiteSpace($JobId) -or [string]::IsNullOrWhiteSpace($Manufacturer) -or [string]::IsNullOrWhiteSpace($Model)) {
throw '-Mode CreateWorkspace requires -JobId, -Manufacturer, and -Model.'
}
$project = New-AutomotiveProject -ProjectJobId $JobId -ProjectManufacturer $Manufacturer `
-ProjectModel $Model -ProjectModelYear $ModelYear -ProjectVIN $VIN `
-ProjectNotes $Notes -ProjectTechnician $Technician
Add-Result 'Workspace' $project.JobId 'Success' $project.LocalPath
$project | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $Paths.Logs "Workspace_$TimeStamp.json") -Encoding UTF8
Write-Log "Workspace created: $($project.LocalPath)" 'OK'
$Results | Export-Csv -LiteralPath $SummaryFile -NoTypeInformation -Encoding UTF8
if ($EnableTranscript) { Stop-Transcript | Out-Null }
return
}
if ($EffectiveMode -eq 'UpdatePortable') {
$SkipWinget = $true
$InstallOptionalApps = $false
}
if (-not (Test-Administrator)) {
throw 'Run this script from Windows PowerShell or PowerShell as Administrator.'
}
if ($EffectiveMode -eq 'Backup') {
Invoke-Safe 'Recovery' 'Backup workstation configuration' {
$archive = Backup-WorkstationConfiguration
Write-Log "Configuration backup created: $archive" 'OK'
} | Out-Null
$Results | Export-Csv -LiteralPath $SummaryFile -NoTypeInformation -Encoding UTF8
if ($EnableTranscript) { Stop-Transcript | Out-Null }
return
}
if ($EffectiveMode -eq 'Restore') {
Invoke-Safe 'Recovery' 'Restore workstation configuration' { Restore-WorkstationConfiguration } | Out-Null
$Results | Export-Csv -LiteralPath $SummaryFile -NoTypeInformation -Encoding UTF8
if ($EnableTranscript) { Stop-Transcript | Out-Null }
return
}
Save-ExecutionCheckpoint -Phase 'Initialize' -Status 'InProgress' -Details 'Prerequisites and administrator check passed.'
Invoke-Safe 'Configuration' 'Disable hibernation' {
$powerProcess = Start-Process -FilePath 'powercfg.exe' -ArgumentList '/hibernate', 'off' -Wait -PassThru -NoNewWindow
if ($powerProcess.ExitCode -ne 0) { throw "powercfg.exe /hibernate off failed with exit code $($powerProcess.ExitCode)." }
} | Out-Null
Invoke-Safe 'Configuration' 'Disable USB selective suspend' {
foreach ($powerArguments in @(
@('/setacvalueindex', 'SCHEME_CURRENT', 'SUB_USB', 'USBSELECTIVE', '0'),
@('/setdcvalueindex', 'SCHEME_CURRENT', 'SUB_USB', 'USBSELECTIVE', '0'),
@('/setactive', 'SCHEME_CURRENT')
)) {
$powerProcess = Start-Process -FilePath 'powercfg.exe' -ArgumentList $powerArguments -Wait -PassThru -NoNewWindow
if ($powerProcess.ExitCode -ne 0) { throw "powercfg.exe $($powerArguments -join ' ') failed with exit code $($powerProcess.ExitCode)." }
}
Invoke-Safe 'Configuration' "Apply Windows settings for $($Profile.Folder)" {
Set-WindowsProfileConfiguration
$windowsState = Get-WindowsConfigurationState
$windowsState | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $Paths.Config "WindowsState_$TimeStamp.json") -Encoding UTF8
} | Out-Null
if ($RestoreConfiguration) {
+80 -85
View File
@@ -1,8 +1,8 @@
# PROJECT COMPLETION SUMMARY
## Automotive Workstation Setup - 9 Phase Audit & Redesign
**Project Completion:** ✅ 2026-09-09
**Status:** All 9 phases complete and documented
**Project Update:** ✅ 2026-09-09
**Status:** Design complete; incremental implementation completed through Week 5
---
@@ -63,15 +63,23 @@
- Example test implementations
- Code coverage expectations
9. **PHASE-9-IMPLEMENTATION.md** (40 hours planned)
9. **PHASE-9-IMPLEMENTATION.md** (implementation roadmap)
- Complete script refactoring roadmap
- 8 modular PowerShell modules
- 14 JSON configuration files (10 new, 4 legacy)
- All 25 issues resolution mapping
- Migration guide and deployment checklist
### 📊 Master Index & Navigation
- **00-PROJECT-MASTER-INDEX.md** — Complete project overview with navigation
### Implemented Work
- [Automotive-Workstation-Setup.ps1](Automotive-Workstation-Setup.ps1) — Weeks 1-4 implementation
- [Config/app-catalog.json](Config/app-catalog.json) — Profile-aware application catalog
- [Config/windows-settings.json](Config/windows-settings.json) — Profile-specific Windows policy
- [Config/workspace-templates.json](Config/workspace-templates.json) — Project workspace templates
- [Tests/Run-AllTests.ps1](Tests/Run-AllTests.ps1) — Pester test runner
- [USER-GUIDE.md](USER-GUIDE.md) and [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — Week 5 documentation
### Master Index & Navigation
- [00-PROJECT-MASTER-INDEX.md](00-PROJECT-MASTER-INDEX.md) — Complete project overview with navigation
---
@@ -85,23 +93,30 @@
| Medium Severity | 12/12 | ✅ All resolved |
| **Total** | **25/25** | **✅ 100%** |
### Features Designed
- ✅ 10 execution modes with distinct workflows
- ✅ Checkpoint-based recovery with restart support
- ✅ 7-layer download validation framework
- ✅ Profile-specific application filtering
- ✅ Automated workspace/project initialization
- ✅ Complete Windows OS configuration per profile
- ✅ Comprehensive health checking and reporting
- ✅ Full backup/restore capability
- ✅ Modular, testable architecture
### Features Implemented
- ✅ Profile-aware application catalog and VS Code extension filtering
- ✅ Parameter-driven installer paths and execution checkpoints
- ✅ HTTPS enforcement with explicit documented HTTP exceptions
- ✅ Portable download size, optional SHA256, archive, and Authenticode checks
- ✅ Profile-specific Windows power and file-system configuration
- ✅ Audit, Plan, Apply, HealthCheck, Backup, Restore, UpdatePortable, and CreateWorkspace paths
- ✅ Automated local/shared workspace creation with metadata and job logs
- ✅ Health, inventory, backup, restore, and structured reporting functions
- ✅ Pester 6 test runner with 10 passing tests
### Remaining Design-Level Work
- ⏳ Full modular split into eight `.psm1` modules
- ⏳ Malware scanning integration and mandatory trusted-hash catalog population
- ⏳ Automatic restart prompting and phase skipping during resume
- ⏳ Dedicated Repair and Inventory dispatch workflows
- ⏳ Full 70+ test expansion described by PHASE-8
### Coverage Provided
- ✅ 9 detailed phase documents (equivalent of 100+ pages)
- ✅ 14 JSON configuration schemas
- ✅ 70+ test specifications
- ✅ Complete API reference (80+ functions planned)
- ✅ User guides, troubleshooting, migration path
- ✅ 10 JSON configuration files, including application, Windows, and workspace catalogs
- ✅ 10 executable Pester tests across configuration, modes, and workspaces
- ✅ User guide and troubleshooting guide
- ✅ Validation checklist and migration/design documentation
---
@@ -110,13 +125,13 @@
| Aspect | Current | New System |
|--------|---------|-----------|
| **Execution Modes** | 1 (Apply only) | 10 (Audit, Plan, Apply, Repair, HealthCheck, Backup, Restore, Inventory, UpdatePortable, CreateWorkspace) |
| **Restart Handling** | Manual/breaks script | Automatic via checkpoint system |
| **Download Validation** | Minimal (existence only) | 7-layer framework (HTTPS, size, archive, SHA256, signature, malware) |
| **Restart Handling** | Manual/breaks script | Checkpoint persistence and explicit resume path |
| **Download Validation** | Minimal (existence only) | HTTPS, size, archive, optional SHA256, and Authenticode checks |
| **Error Recovery** | Requires manual rerun | Automated via Repair mode + checkpoints |
| **Profile Differentiation** | None (identical apps) | Full (AllowList/DenyList per profile) |
| **Workspace Support** | Manual folder creation | Automated templates with metadata |
| **Windows Config** | Hard-coded values | Profile-specific, reversible, testable |
| **Testing** | Ad-hoc manual | 70+ automated Pester tests |
| **Testing** | Ad-hoc manual | 10 automated Pester tests, with expansion planned |
| **Logging** | Minimal | Comprehensive audit trail + structured logs |
| **Documentation** | Sparse | 9 guides (100+ pages equivalent) |
@@ -144,39 +159,39 @@
---
## 10 EXECUTION MODES AT A GLANCE
## EXECUTION MODES
```
Audit Mode → Scan current state (no changes)
Plan Mode → Show what will be installed/changed
Apply Mode → Full installation with checkpoint recovery
Repair Mode → Fix failed installs from previous runs
HealthCheck Mode → Verify all installations are correct
Backup Mode → Save current state for rollback
Restore Mode → Rollback to previous state
Inventory Mode → Export list of installed applications
UpdatePortable Mode → Update portable apps to latest versions
CreateWorkspace → Initialize new project with templates
Audit Mode → Scan current state (no setup changes)
Plan Mode → Generate Windows and application plans
Apply Mode → Full installation with checkpoint persistence
Repair Mode → Accepted mode; currently follows idempotent apply behavior
HealthCheck Mode → Verify workstation health
Backup Mode → Save current configuration
Restore Mode → Restore the latest configuration backup
Inventory Mode → Inventory support through the audit/reporting path
UpdatePortable Mode → Run portable-app flow without WinGet
CreateWorkspace → Initialize a profile-specific project
```
---
## CHECKPOINT & RESTART RECOVERY
The new system can **resume after a forced restart** (WinGet often requires reboot):
The current system persists execution checkpoints and can be explicitly resumed after a restart:
1. **Execution starts** → Create checkpoint with ExecutionId
2. **Phase completes** → Update checkpoint, log progress
3. **Restart detected** → Save state to disk, prompt user
4. **System reboots** → Checkpoint persisted, waiting for resume
5. **Script resumes** → Load checkpoint, skip completed phases
6. **Continue from where it left off** → Full transparency and recovery
5. **Script resumes** → Load checkpoint and revalidate prior work
6. **Continue safely** → Idempotent installers continue from the saved execution context
---
## SECURITY FRAMEWORK (7 LAYERS)
## DOWNLOAD SECURITY
Every download validates through:
The implementation currently validates downloads through:
1. **HTTPS Enforcement** — Encrypted, prevents man-in-the-middle
2. **File Size Check** — Detects truncation or injection
@@ -184,42 +199,23 @@ Every download validates through:
4. **Expected Files** — Verifies correct files extracted
5. **SHA256 Hash** — Cryptographic integrity verification
6. **Authenticode** — Digital signature on executables
7. **Malware Scan** — Windows Defender scan (optional)
7. **Malware Scan** — Reserved for the planned security expansion
HTTP allowed only on documented allow-list with strong justification.
---
## NEXT STEPS FOR IMPLEMENTATION
## NEXT STEPS
### Week 1-2: Core Infrastructure
- [ ] Refactor script parameters and configuration loading
- [ ] Implement checkpoint/recovery system
- [ ] Create logging framework
- [ ] Fix critical path derivation bug
- [ ] Add admin privilege check
### Week 2-3: Applications & Validation
- [ ] Create unified app-catalog.json
- [ ] Implement WinGet installation with retry
- [ ] Implement 7-layer download validation
- [ ] Fix MVCI PRO duplication
- [ ] Test on first profile
### Week 3-4: Features & Modes
- [ ] Implement all 10 execution modes
- [ ] Create Windows configuration functions
- [ ] Implement workspace templates
- [ ] Add HealthCheck and Repair logic
- [ ] Create backup/restore functionality
### Week 4-5: Testing & Deployment
- [ ] Run full Pester test suite (70+ tests)
- [ ] Test restart recovery thoroughly
- [ ] Complete user documentation
- [ ] Deploy to test workstations
- [ ] Gather feedback and refine
- [ ] Deploy to production
### Remaining implementation
- [ ] Split the monolithic script into the planned PowerShell modules
- [ ] Complete dedicated Repair and Inventory mode dispatch
- [ ] Add automatic restart prompting and phase skipping
- [ ] Populate and enforce trusted SHA256 values for portable releases
- [ ] Add Windows Defender malware scanning integration
- [ ] Expand the Pester suite toward the PHASE-8 target
- [ ] Run elevated Apply tests on clean profile workstations
- [ ] Complete deployment packaging and production rollout
---
@@ -249,7 +245,8 @@ HTTP allowed only on documented allow-list with strong justification.
- **Execution Modes:** 10
- **Application Profiles:** 3
- **Applications Cataloged:** 65+
- **Tests Specified:** 70+
- **Tests Implemented:** 10 passing Pester tests
- **Tests Planned:** 70+
- **JSON Schemas:** 14
- **Functions Designed:** 80+
@@ -258,9 +255,9 @@ HTTP allowed only on documented allow-list with strong justification.
## QUALITY METRICS
### Coverage
- **Code Coverage (Target):** 87.5%
- **Code Coverage:** Not measured yet
- **Documentation Coverage:** 100% (all issues documented)
- **Test Coverage:** 100% (all modes tested)
- **Behavioral Test Coverage:** Configuration, read-only modes, path safety, and all workspace profiles
### Issues
- **Critical Issues Fixed:** 5/5 (100%)
@@ -279,15 +276,15 @@ HTTP allowed only on documented allow-list with strong justification.
## RECOMMENDATION FOR NEXT PHASE
**Ready to begin Phase 9 (Implementation)?**
**Ready for the remaining hardening and deployment work?**
The design and specification are complete. All dependencies, requirements, and architectural decisions have been documented. You can now:
The design is complete and the first five implementation weeks are validated. The remaining work is focused on hardening and production rollout:
1. **Allocate development resources** — ~40 hours for Phase 9 implementation
2. **Review specifications** — Ensure all phases meet requirements
3. **Begin script refactoring** — Start with core infrastructure (Week 1)
4. **Follow the roadmap** — 5-week implementation timeline
5. **Deploy and validate** — Test on clean VMs, iterate, deploy to production
1. **Complete remaining mode semantics** — Repair, Inventory, and automatic resume skipping
2. **Harden download trust** — Populate trusted hashes and add malware scanning
3. **Split modules** — Extract configuration, download, Windows, workspace, and reporting modules
4. **Run elevated Apply tests** — Validate WinGet, portable downloads, drivers, and restart behavior
5. **Package and deploy** — Test on clean workstations before production rollout
All 9 phase documents are ready to guide development. The specifications are detailed enough for a developer to implement without extensive back-and-forth.
@@ -307,17 +304,15 @@ For questions about:
---
**Project Status:** ✅ **DESIGN & SPECIFICATION COMPLETE**
**Project Status:** ✅ **DESIGN COMPLETE; WEEKS 1-5 IMPLEMENTED AND VALIDATED**
All 9 phases are fully documented and ready for implementation.
All 9 phase documents are complete. The implementation now includes profile-aware applications, secure download gates, Windows configuration, checkpoint persistence, workspace initialization, automated tests, and operator documentation.
The automotive workstation setup system has been comprehensively audited, architecturally redesigned, and thoroughly specified for production-grade deployment.
**Ready to build! 🚀**
The remaining work is production hardening and elevated workstation validation.
---
*Last Updated: 2026-09-09*
*Project Duration: ~172 hours of design and specification work*
*Implementation Timeline: ~4-5 weeks*
*Project Duration: Design plus five implementation weeks*
*Last validation: 10 Pester tests passed*
+38
View File
@@ -0,0 +1,38 @@
{
"SchemaVersion": "1.0",
"Profiles": {
"DailyTech-Tuning": {
"PowerPlan": "High performance",
"PowerPlanAlias": "SCHEME_MIN",
"Hibernation": "Disabled",
"UsbSelectiveSuspend": "Disabled",
"MonitorTimeoutACMinutes": 0,
"DiskTimeoutACMinutes": 0,
"StandbyTimeoutACMinutes": 0,
"ShowFileExtensions": true,
"LongPathsEnabled": true
},
"ODIS-XENTRY": {
"PowerPlan": "Balanced",
"PowerPlanAlias": "SCHEME_BALANCED",
"Hibernation": "Disabled",
"UsbSelectiveSuspend": "Disabled",
"MonitorTimeoutACMinutes": 30,
"DiskTimeoutACMinutes": 0,
"StandbyTimeoutACMinutes": 60,
"ShowFileExtensions": true,
"LongPathsEnabled": true
},
"PIWIS-ISTA": {
"PowerPlan": "Balanced",
"PowerPlanAlias": "SCHEME_BALANCED",
"Hibernation": "Disabled",
"UsbSelectiveSuspend": "Disabled",
"MonitorTimeoutACMinutes": 30,
"DiskTimeoutACMinutes": 0,
"StandbyTimeoutACMinutes": 60,
"ShowFileExtensions": true,
"LongPathsEnabled": true
}
}
}
+56
View File
@@ -0,0 +1,56 @@
{
"SchemaVersion": "1.0",
"Templates": {
"DailyTech-Tuning": {
"LocalBase": "Projects\\Tuning",
"SharedBase": "",
"Folders": [
"Originals",
"RepeatedReads",
"WorkingCopies\\v1",
"Checksums",
"BinaryDifferences",
"Backups",
"Reports",
"Logs",
"CAN_Data\\DBC_Files",
"Scripts",
"Documentation",
"Photos",
"FinalDelivery",
"ArchivedProjects"
],
"OperationType": "Tuning"
},
"ODIS-XENTRY": {
"LocalBase": "Projects\\Diagnostics\\ODIS",
"SharedBase": "",
"Folders": [
"SessionLogs",
"CodingBackups",
"FlashFiles",
"Screenshots",
"DiagnosticReports",
"CAN_Data",
"Configuration",
"Documentation"
],
"OperationType": "Diagnostics"
},
"PIWIS-ISTA": {
"LocalBase": "Projects\\Diagnostics\\PIWIS",
"SharedBase": "",
"Folders": [
"PIWIS_Sessions",
"PSdZData",
"CodingBackups",
"ISTA_Logs",
"Screenshots",
"MeasuringPlans",
"Reports",
"Documentation"
],
"OperationType": "Diagnostics"
}
}
}
+38
View File
@@ -0,0 +1,38 @@
# Troubleshooting
## Pester is not installed
Install Pester for the current user, then rerun the test runner:
```powershell
Install-Module Pester -Scope CurrentUser -Force
.\Tests\\Run-AllTests.ps1
```
## Script reports that administrator privileges are required
`Apply`, `Repair`, `Restore`, `UpdatePortable`, and Windows configuration changes require an elevated Windows PowerShell window. Use `Plan`, `Audit`, `HealthCheck`, or `CreateWorkspace` when elevation is not available.
## A shared drive is unavailable
The setup expects `S:` for shared automotive data and `P:` for shared portable applications. `HealthCheck` reports their availability and labels. Portable applications fall back to the local `C:\Automotive\PortableApps` path when `P:` is unavailable, but shared profile data remains unavailable until the drive is mounted.
## A download is blocked as insecure
Downloads must use HTTPS. HTTP is rejected unless the caller explicitly marks a documented vendor exception. The current exception is limited to the Xhorse assets whose vendor CDN does not provide HTTPS. Verify the vendor network/VPN path and confirm the URL before retrying.
## A portable download fails hash or signature validation
Do not bypass the failure. Preserve the downloaded file, compare its SHA256 value with the trusted vendor release information, and inspect the Authenticode status. Remove the file only after the incident has been recorded. Update `Config\\portable-apps.json` only with a verified expected hash.
## Resume after restart
Check `C:\Automotive\Config\\.workstation-setup-state.json` or the configured local root for the last checkpoint. Resume with the same profile and `-ResumeFromCheckpoint`. Completed work is revalidated because installers and vendor packages can change between runs.
## Workspace creation fails
`CreateWorkspace` requires `-JobId`, `-Manufacturer`, and `-Model`. Job IDs, manufacturers, and models cannot contain path separators or Windows filename characters. Use a simple value such as `VW001`, `Volkswagen`, and `Golf`.
## OEM or communication software installation
The setup does not silently install licensed OEM suites. Use the vendor installer, complete licensing and device registration on the current Windows installation, and verify J2534/USB drivers separately. Keep other isolated Windows installations without drive letters while working in the current profile.
+37
View File
@@ -0,0 +1,37 @@
Describe 'Configuration integrity' {
BeforeAll {
$script:repositoryRoot = Split-Path $PSScriptRoot -Parent
$script:configRoot = Join-Path $script:repositoryRoot 'Config'
}
It 'parses every JSON configuration file' {
$files = @(Get-ChildItem -LiteralPath $script:configRoot -Filter '*.json' -File)
$files.Count | Should -BeGreaterThan 0
foreach ($file in $files) {
{ Get-Content -LiteralPath $file.FullName -Raw | ConvertFrom-Json -ErrorAction Stop } | Should -Not -Throw
}
}
It 'defines all supported workstation profiles' {
$profiles = Get-Content (Join-Path $script:configRoot 'workstation-profiles.json') -Raw | ConvertFrom-Json
@($profiles.Profiles.Name) | Should -Contain 'DailyTech & Tuning'
@($profiles.Profiles.Name) | Should -Contain 'ODIS & XENTRY'
@($profiles.Profiles.Name) | Should -Contain 'PIWIS & ISTA'
}
It 'defines Windows settings and workspace templates for every profile folder' {
$profiles = Get-Content (Join-Path $script:configRoot 'workstation-profiles.json') -Raw | ConvertFrom-Json
$windows = Get-Content (Join-Path $script:configRoot 'windows-settings.json') -Raw | ConvertFrom-Json
$templates = Get-Content (Join-Path $script:configRoot 'workspace-templates.json') -Raw | ConvertFrom-Json
foreach ($profile in $profiles.Profiles) {
$windows.Profiles.PSObject.Properties.Name | Should -Contain $profile.Folder
$templates.Templates.PSObject.Properties.Name | Should -Contain $profile.Folder
}
}
It 'contains no duplicate application catalog IDs' {
$catalog = Get-Content (Join-Path $script:configRoot 'app-catalog.json') -Raw | ConvertFrom-Json
$ids = @($catalog.Packages.Id)
@($ids | Select-Object -Unique).Count | Should -Be $ids.Count
}
}
+40
View File
@@ -0,0 +1,40 @@
Describe 'Execution modes' {
BeforeAll {
$script:repositoryRoot = Split-Path $PSScriptRoot -Parent
$script:scriptPath = Join-Path $script:repositoryRoot 'Automotive-Workstation-Setup.ps1'
$script:newTestRoot = { Join-Path ([IO.Path]::GetTempPath()) ('automotive-pester-' + [guid]::NewGuid().ToString('N')) }
}
It 'generates a plan without requiring administrator privileges' {
$root = & $script:newTestRoot
try {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script:scriptPath -Mode Plan `
-LocalRoot $root -SharedDataRoot (Join-Path $root 'SharedData') `
-SharedPortableRoot (Join-Path $root 'PortableApps')
$LASTEXITCODE | Should -Be 0
Get-ChildItem -LiteralPath (Join-Path $root 'Logs') -Filter '*ApplicationPlan*.csv' | Should -Not -BeNullOrEmpty
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
}
It 'runs HealthCheck without entering the administrator-gated apply path' {
$root = & $script:newTestRoot
try {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script:scriptPath -Mode HealthCheck `
-LocalRoot $root -SharedDataRoot (Join-Path $root 'SharedData') `
-SharedPortableRoot (Join-Path $root 'PortableApps') -SkipDownloads -SkipWinget -SkipShortcuts
$LASTEXITCODE | Should -Be 0
Get-ChildItem -LiteralPath $root -Recurse -Filter 'HealthReport_*.json' | Should -Not -BeNullOrEmpty
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
}
It 'does not contain the old hard-coded installer root' {
$content = Get-Content -LiteralPath $script:scriptPath -Raw
$content | Should -Not -Match '\$ProfileInstallerRoot\s*=\s*["'']P:\\Install'
}
}
+16
View File
@@ -0,0 +1,16 @@
[CmdletBinding()]
param(
[string]$TestPath
)
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($TestPath)) {
$TestPath = Split-Path -Parent $MyInvocation.MyCommand.Path
}
if (-not (Get-Module -ListAvailable -Name Pester)) {
throw 'Pester is not installed. Install it with: Install-Module Pester -Scope CurrentUser -Force'
}
Import-Module Pester -MinimumVersion 5.0 -ErrorAction Stop
$result = Invoke-Pester -Path (Join-Path $TestPath '*.Tests.ps1') -Output Detailed -PassThru
if ($result.FailedCount -gt 0) { exit 1 }
+54
View File
@@ -0,0 +1,54 @@
Describe 'CreateWorkspace mode' {
BeforeAll {
$script:repositoryRoot = Split-Path $PSScriptRoot -Parent
$script:scriptPath = Join-Path $script:repositoryRoot 'Automotive-Workstation-Setup.ps1'
$script:newTestRoot = { Join-Path ([IO.Path]::GetTempPath()) ('automotive-workspace-pester-' + [guid]::NewGuid().ToString('N')) }
}
It 'creates the DailyTech template and metadata' {
$root = & $script:newTestRoot
try {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script:scriptPath -Mode CreateWorkspace `
-WorkstationProfile 'DailyTech & Tuning' -LocalRoot $root `
-SharedDataRoot (Join-Path $root 'SharedData') -SharedPortableRoot (Join-Path $root 'PortableApps') `
-JobId 'TEST001' -Manufacturer 'Volkswagen' -Model 'Golf' -Module ECU
$LASTEXITCODE | Should -Be 0
$project = Get-ChildItem (Join-Path $root 'Projects\Tuning') -Directory | Select-Object -First 1
Test-Path (Join-Path $project.FullName 'ProjectMetadata.json') | Should -BeTrue
Test-Path (Join-Path $project.FullName 'Originals') | Should -BeTrue
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
}
It 'rejects path traversal in JobId' {
$root = & $script:newTestRoot
try {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script:scriptPath -Mode CreateWorkspace `
-LocalRoot $root -SharedDataRoot (Join-Path $root 'SharedData') -SharedPortableRoot (Join-Path $root 'PortableApps') `
-JobId '..\escape' -Manufacturer 'BMW' -Model 'Test'
$LASTEXITCODE | Should -Not -Be 0
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
}
It 'creates profile-specific diagnostic roots' {
foreach ($profile in @('ODIS & XENTRY', 'PIWIS & ISTA')) {
$root = & $script:newTestRoot
try {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script:scriptPath -Mode CreateWorkspace `
-WorkstationProfile $profile -LocalRoot $root `
-SharedDataRoot (Join-Path $root 'SharedData') -SharedPortableRoot (Join-Path $root 'PortableApps') `
-JobId 'TEST002' -Manufacturer 'BMW' -Model 'Test Model' -Module ECU
$LASTEXITCODE | Should -Be 0
Get-ChildItem (Join-Path $root 'Projects\Diagnostics') -Directory -Recurse | Where-Object Name -like 'TEST002_*' | Should -Not -BeNullOrEmpty
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
}
+61
View File
@@ -0,0 +1,61 @@
# Automotive Workstation Setup User Guide
## Modes
Run the script from an elevated Windows PowerShell session for installation modes. Read-only modes do not require elevation.
```powershell
# Preview changes
.\Automotive-Workstation-Setup.ps1 -Mode Plan -WorkstationProfile 'DailyTech & Tuning'
# Inspect the current workstation
.\Automotive-Workstation-Setup.ps1 -Mode Audit -WorkstationProfile 'ODIS & XENTRY'
# Apply the selected profile
.\Automotive-Workstation-Setup.ps1 -Mode Apply -WorkstationProfile 'PIWIS & ISTA'
# Create a project workspace
.\Automotive-Workstation-Setup.ps1 -Mode CreateWorkspace `
-WorkstationProfile 'DailyTech & Tuning' `
-JobId 'VW001' -Manufacturer 'Volkswagen' -Model 'Golf' `
-ModelYear 2015 -Module ECU -Technician 'Technician Name'
```
`-HealthCheck`, `-BackupConfiguration`, and `-RestoreConfiguration` remain supported for compatibility with older commands.
## Profiles
- `DailyTech & Tuning`: tuning, binary analysis, reverse engineering, and CAN work.
- `ODIS & XENTRY`: VAG and Mercedes diagnostic sessions.
- `PIWIS & ISTA`: Porsche and BMW diagnostic sessions.
The profile determines application selection, Windows power policy, and workspace structure.
## Reports and State
Reports are written under the configured local root:
- `Logs`: setup logs, CSV summaries, plans, and workspace results.
- `Config`: local profile metadata, Windows state, and checkpoint state.
- `Manifests`: portable application hashes and inventories when the shared portable root is available.
The checkpoint file is `Config\\.workstation-setup-state.json`. Resume a prior apply run with:
```powershell
.\Automotive-Workstation-Setup.ps1 -Mode Apply `
-WorkstationProfile 'DailyTech & Tuning' -ResumeFromCheckpoint
```
## Workspace Data Handling
Always preserve original ECU, EEPROM, and coding reads in the `Originals` or profile-equivalent folder. Calculate hashes before editing. Keep modified files in `WorkingCopies`, `CodingBackups`, or the matching profile folder. Do not flash or code a vehicle without authorization, verified backups, stable power, and a vendor-supported procedure.
## Testing
Run the Pester suite from the repository root:
```powershell
.\Tests\\Run-AllTests.ps1
```
The suite is designed to use temporary roots and does not install applications or modify Windows settings.