diff --git a/.gitignore b/.gitignore index 8e4258aa..de0af7d7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,6 @@ winutil.pdb -### Preprocessor Hashes ### -.preprocessor_hashes.json - ### Windows ### # Folder config file @@ -54,8 +51,6 @@ winutil.ps1 binary/ -.preprocessor_hashes.json - # Hugo Files docs/public/ docs/.hugo_build.lock diff --git a/Compile.ps1 b/Compile.ps1 index ba23652a..23b81d6e 100644 --- a/Compile.ps1 +++ b/Compile.ps1 @@ -1,144 +1,50 @@ param ( - [switch]$Run, - [string]$Arguments + [switch]$Run ) -if ((Get-Item ".\winutil.ps1" -ErrorAction SilentlyContinue).IsReadOnly) { - Remove-Item ".\winutil.ps1" -Force -} - -$OFS = "`r`n" -$scriptname = "winutil.ps1" -$workingdir = $PSScriptRoot +$OFS = "`r`n" # Makes it so we dont need to add -Raw to every Get-Content command # Variable to sync between runspaces $sync = [Hashtable]::Synchronized(@{}) $sync.configs = @{} -function Update-Progress { - param ( - [Parameter(Mandatory, position=0)] - [string]$StatusMessage, +# Create the script in memory. +$script = [System.Collections.Generic.List[string]]::new() - [Parameter(Mandatory, position=1)] - [ValidateRange(0,100)] - [int]$Percent, - - [Parameter(position=2)] - [string]$Activity = "Compiling" - ) - - Write-Progress -Activity $Activity -Status $StatusMessage -PercentComplete $Percent -} - -Update-Progress "Pre-req: Running Preprocessor..." 0 - -# Dot source the 'Invoke-Preprocessing' Function from 'tools/Invoke-Preprocessing.ps1' Script -$preprocessingFilePath = ".\tools\Invoke-Preprocessing.ps1" -. $preprocessingFilePath - -$excludedFiles = @() - -# Add directories only if they exist -if (Test-Path '.\.git\') { $excludedFiles += '.\.git\' } -if (Test-Path '.\binary\') { $excludedFiles += '.\binary\' } - -# Add files that should always be excluded -$excludedFiles += @( - '.\.gitignore', - '.\.gitattributes', - '.\.github\CODEOWNERS', - '.\LICENSE', - "$preprocessingFilePath", - '*.png', - '.\.preprocessor_hashes.json' +$script.Add( + ((Get-Content -Path scripts\start.ps1) -replace '#{replaceme}', (Get-Date -Format 'yy.MM.dd')) ) -$msg = "Pre-req: Code Formatting" -Invoke-Preprocessing -WorkingDir "$workingdir" -ExcludedFiles $excludedFiles -ProgressStatusMessage $msg +$script.Add((Get-ChildItem -Path functions -Recurse -File | Get-Content)) -# Create the script in memory. -Update-Progress "Pre-req: Allocating Memory" 0 -$script_content = [System.Collections.Generic.List[string]]::new() +Get-ChildItem config | ForEach-Object { + $obj = Get-Content -Path $_.FullName | ConvertFrom-Json -Update-Progress "Adding: Version" 10 -$script_content.Add($(Get-Content "scripts\start.ps1").replace('#{replaceme}',"$(Get-Date -Format yy.MM.dd)")) - -Update-Progress "Adding: Functions" 20 -Get-ChildItem "functions" -Recurse -File | ForEach-Object { - $script_content.Add($(Get-Content $psitem.FullName)) - } -Update-Progress "Adding: Config *.json" 40 -Get-ChildItem "config" | Where-Object {$psitem.extension -eq ".json"} | ForEach-Object { - $json = (Get-Content $psitem.FullName -Raw) - $jsonAsObject = $json | ConvertFrom-Json - - # Add 'WPFInstall' as a prefix to every entry-name in 'applications.json' file - if ($psitem.Name -eq "applications.json") { - foreach ($appEntryName in $jsonAsObject.PSObject.Properties.Name) { - $appEntryContent = $jsonAsObject.$appEntryName - $jsonAsObject.PSObject.Properties.Remove($appEntryName) - $jsonAsObject | Add-Member -MemberType NoteProperty -Name "WPFInstall$appEntryName" -Value $appEntryContent + if ($_.Name -eq "applications.json") { + $fixed = [ordered]@{} + foreach ($p in $obj.PSObject.Properties) { + $fixed["WPFInstall$($p.Name)"] = $p.Value } + $obj = [pscustomobject]$fixed } - # Line 90 requires no whitespace inside the here-strings, to keep formatting of the JSON in the final script. - $json = @" -$($jsonAsObject | ConvertTo-Json -Depth 3) -"@ + $json = $obj | ConvertTo-Json -Depth 10 - $sync.configs.$($psitem.BaseName) = $json | ConvertFrom-Json - $script_content.Add($(Write-Output "`$sync.configs.$($psitem.BaseName) = @'`r`n$json`r`n'@ `| ConvertFrom-Json" )) + $sync.configs[$_.BaseName] = $obj + $script.Add("`$sync.configs.$($_.BaseName) = @'`r`n$json`r`n'@ | ConvertFrom-Json") } # Read the entire XAML file as a single string, preserving line breaks -$xaml = Get-Content "$workingdir\xaml\inputXML.xaml" -Raw +$xaml = Get-Content -Path xaml\inputXML.xaml +$script.Add('$inputXML = @''' + "`n" + $xaml + "`n" + '''@') -Update-Progress "Adding: Xaml " 90 +$autounattendXml = Get-Content -Path tools\autounattend.xml +$script.Add("`$WinUtilAutounattendXml = @'`r`n$autounattendXml`r`n'@") -# Add the XAML content to $script_content using a here-string -$script_content.Add(@" -`$inputXML = @' -$xaml -'@ -"@) +$script.Add((Get-Content -Path scripts\main.ps1)) -Update-Progress "Adding: autounattend.xml" 95 -$autounattendRaw = Get-Content "$workingdir\tools\autounattend.xml" -Raw -# Strip XML comments (, including multi-line) -$autounattendRaw = [regex]::Replace($autounattendRaw, '', '', [System.Text.RegularExpressions.RegexOptions]::Singleline) -# Drop blank lines and trim trailing whitespace per line -$autounattendXml = ($autounattendRaw -split "`r?`n" | - Where-Object { $_.Trim() -ne '' } | - ForEach-Object { $_.TrimEnd() }) -join "`r`n" -$script_content.Add(@" -`$WinUtilAutounattendXml = @' -$autounattendXml -'@ -"@) +Set-Content -Path winutil.ps1 -Value $script -$script_content.Add($(Get-Content "scripts\main.ps1")) - -Update-Progress "Removing temporary files" 99 -Remove-Item "xaml\inputApp.xaml" -ErrorAction SilentlyContinue -Remove-Item "xaml\inputTweaks.xaml" -ErrorAction SilentlyContinue -Remove-Item "xaml\inputFeatures.xaml" -ErrorAction SilentlyContinue - -Set-Content -Path "$scriptname" -Value ($script_content -join "`r`n") -Encoding ascii -Write-Progress -Activity "Compiling" -Completed - -Update-Progress -Activity "Validating" -StatusMessage "Checking winutil.ps1 Syntax" -Percent 0 -try { - Get-Command -Syntax .\winutil.ps1 | Out-Null -} catch { - Write-Warning "Syntax Validation for 'winutil.ps1' has failed" - Write-Host "$($Error[0])" -ForegroundColor Red - exit 1 -} -Write-Progress -Activity "Validating" -Completed - -if ($run) { - Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass - .\Winutil.ps1 $Arguments - break +if ($Run) { + .\Winutil.ps1 } diff --git a/tools/Invoke-Preprocessing.ps1 b/tools/Invoke-Preprocessing.ps1 deleted file mode 100644 index 0afa1a1b..00000000 --- a/tools/Invoke-Preprocessing.ps1 +++ /dev/null @@ -1,164 +0,0 @@ -function Invoke-Preprocessing { - <# - .SYNOPSIS - A function that does Code Formatting using RegEx, useful when trying to force specific coding standard(s) to a project. - - .PARAMETER ExcludedFiles - A list of file paths which're *relative to* 'WorkingDir' Folder, every item in the list can be pointing to File (doesn't end with '\') or Directory (ends with '\') or None-Existing File/Directory. - By default, it checks if everyitem exists, and throws an exception if one or more are not found (None-Existing). - - .PARAMETER WorkingDir - The folder to search inside recursively for files which're going to be Preprocessed (Code Formatted), unless they're found in 'ExcludedFiles' List. - Note: The path should be absolute, NOT relative. - - .PARAMETER ProgressStatusMessage - The status message used when displaying the progress bar, which's done through PowerShell 'Write-Progress' Cmdlet. - This's a Required Parameter, as the information displayed to terminal is useful when running this function, - which might take less than 1 sec to minutes depending on project's scale & hardware performance. - - .PARAMETER ProgressActivity - The activity message used when displaying the progress bar, which's done through PowerShell 'Write-Progress' Cmdlet, - This's an Optional Parameter, default value is 'Preprocessing', used in combination with 'ProgressStatusMessage' Parameter Value. - - .EXAMPLE - Invoke-Preprocessing -WorkingDir "DRIVE:\Path\To\Folder\" -ExcludedFiles @('file.txt', '.\.git\', '*.png') -ProgressStatusMessage "Doing Preprocessing" - - Calls 'Invoke-Preprocessing' function using Named Parameters, with 'WorkingDir' (Mandatory Parameter) which's used as the base folder when searching for files recursively (using 'Get-ChildItem'), other two parameters are, in order from right to left, the Optional 'ExcludeFiles', which can be a path to a file, folder, or pattern-matched (like '*.png'), and the 'ProgressStatusMessage', which's used in Progress Bar. - - .EXAMPLE - Invoke-Preprocessing -WorkingDir "DRIVE:\Path\To\Folder\" -ExcludedFiles @('file.txt', '.\.git\', '*.png') -ProgressStatusMessage "Doing Preprocessing" -ProgressActivity "Re-Formatting Code" - - Same as Example No. 1, but uses 'ProgressActivity' which's used in Progress Bar. - - .EXAMPLE - Invoke-Preprocessing -Skip -WorkingDir "DRIVE:\Path\To\Folder\" -ExcludedFiles @('file.txt', '.\.git\', '*.png') -ProgressStatusMessage "Doing Preprocessing" - - #> - - param ( - [Parameter(Mandatory, position=1)] - [ValidateScript({[System.IO.Path]::IsPathRooted($_)})] - [string]$WorkingDir, - - [Parameter(position=2)] - [string[]]$ExcludedFiles, - - [Parameter(Mandatory, position=3)] - [string]$ProgressStatusMessage, - - [Parameter(position=4)] - [string]$ProgressActivity = "Preprocessing" - ) - - if (-NOT (Test-Path -PathType Container -Path "$WorkingDir")) { - throw "[Invoke-Preprocessing] Invalid Parameter Value for 'WorkingDir', passed value: '$WorkingDir'. Either the path is a File or Non-Existing/Invlid, please double check your code." - } - - $InternalExcludedFiles = [System.Collections.Generic.List[string]]::new($ExcludedFiles.Count) - ForEach ($excludedFile in $ExcludedFiles) { - $InternalExcludedFiles.Add($excludedFile) | Out-Null - } - - # Validate the ExcludedItems List before continuing on - if ($ExcludedFiles.Count -gt 0) { - ForEach ($excludedFile in $ExcludedFiles) { - $filePath = "$(($WorkingDir -replace ('\\$', '')) + '\' + ($excludedFile -replace ('\.\\', '')))" - # Only attempt to create the directory if the excludedFile ends with '\' - if ($excludedFile -match '\\$' -and -not (Test-Path "$filePath")) { - New-Item -Path "$filePath" -ItemType Directory -Force | Out-Null - } - $files = Get-ChildItem -Recurse -Path "$filePath" -File -Force - if ($files.Count -gt 0) { - ForEach ($file in $files) { - $InternalExcludedFiles.Add("$($file.FullName)") | Out-Null - } - } else { $failedFilesList += "'$filePath', " } - } - $failedFilesList = $failedFilesList -replace (',\s*$', '') - } - - # Get Files List - [System.Collections.ArrayList]$files = Get-ChildItem -LiteralPath $WorkingDir -Recurse -Exclude $InternalExcludedFiles -File -Force - - # Only keep the 'FullName' Property for every entry in the list - for ($i = 0; $i -lt $files.Count; $i++) { - $file = $files[$i] - $files[$i] = $file.FullName - } - - # If a file(s) are found in Exclude List, - # Remove the file from files list. - ForEach ($excludedFile in $InternalExcludedFiles) { - $index = $files.IndexOf("$excludedFile") - if ($index -ge 0) { $files.RemoveAt($index) } - } - - # Define a path to store the file hashes - $hashFilePath = Join-Path -Path $WorkingDir -ChildPath ".preprocessor_hashes.json" - - # Load existing hashes if the file exists - $existingHashes = @{} - if (Test-Path -Path $hashFilePath) { - # intentionally dosn't use ConvertFrom-Json -AsHashtable as it isn't supported on old powershell versions - $file_content = Get-Content -Path $hashFilePath | ConvertFrom-Json - foreach ($property in $file_content.PSObject.Properties) { - $existingHashes[$property.Name] = $property.Value - } - } - - $newHashes = @{} - $changedFiles = @() - $hashingAlgorithm = "MD5" - foreach ($file in $files){ - # Calculate the hash of the file - $hash = Get-FileHash -Path $file -Algorithm $hashingAlgorithm | Select-Object -ExpandProperty Hash - $newHashes[$file] = $hash - - # Check if the hash already exists in the existing hashes - if (($existingHashes.ContainsKey($file) -and $existingHashes[$file] -eq $hash)) { - # Skip processing this file as it hasn't changed - continue; - } - else { - # If the hash doesn't exist or has changed, add it to the changed files list - $changedFiles += $file - } - } - - $files = $changedFiles - $numOfFiles = $files.Count - Write-Debug "[Invoke-Preprocessing] Files Changed: $numOfFiles" - - if ($numOfFiles -eq 0){ - Write-Debug "[Invoke-Preprocessing] Found 0 Files to Preprocess inside 'WorkingDir' Directory : '$WorkingDir'." - return - } - - for ($i = 0; $i -lt $numOfFiles; $i++) { - $fullFileName = $files[$i] - - # TODO: - # make more formatting rules, and document them in WinUtil Official Documentation - (Get-Content "$fullFileName").TrimEnd() ` - -replace ('\t', ' ') ` - -replace ('\)\s*\{', ') {') ` - -replace ('(?if|for|foreach)\s*(?\([.*?]\))\s*\{', '${keyword} ${condition} {') ` - -replace ('\}\s*elseif\s*(?\([.*?]\))\s*\{', '} elseif ${condition} {') ` - -replace ('\}\s*else\s*\{', '} else {') ` - -replace ('Try\s*\{', 'try {') ` - -replace ('Catch\s*\{', 'catch {') ` - -replace ('\}\s*Catch', '} catch') ` - -replace ('\}\s*Catch\s*(?(\[.*?\]\s*(\,)?\s*)+)\s*\{', '} catch ${exceptions} {') ` - -replace ('\}\s*Catch\s*(?\[.*?\])\s*\{', '} catch ${exceptions} {') ` - -replace ('(?\[[^$0-9]+\])\s*(?\$.*?)', '${parameter_type}${str_after_type}') ` - | Set-Content "$fullFileName" - $newHashes[$fullFileName] = Get-FileHash -Path $fullFileName -Algorithm $hashingAlgorithm | Select-Object -ExpandProperty Hash - - Write-Progress -Activity $ProgressActivity -Status "$ProgressStatusMessage - Finished $i out of $numOfFiles" -PercentComplete (($i/$numOfFiles)*100) - } - - Write-Progress -Activity $ProgressActivity -Status "$ProgressStatusMessage - Finished Task Successfully" -Completed - - # Save the new hashes to the file - $newHashes | ConvertTo-Json -Depth 10 | Set-Content -Path $hashFilePath -}