diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 0000000..55faaf5 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,28 @@ +name: CI Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + + - name: Restore packages + run: nuget restore MicroWin/MicroWin.csproj -PackagesDirectory packages + + - name: Build Release + run: msbuild MicroWin/MicroWin.csproj /p:Configuration=Release /p:Platform=AnyCPU /verbosity:minimal \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..013007b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.preferCSharpExtension": true +} \ No newline at end of file diff --git a/Bhelper.ps1 b/Bhelper.ps1 new file mode 100644 index 0000000..3cbd205 --- /dev/null +++ b/Bhelper.ps1 @@ -0,0 +1,333 @@ +param( + [Parameter(Mandatory = $true)] + [ValidateSet('Build')] + [string]$Action, + + [Parameter(Mandatory = $true)] + [string]$ScriptDir +) + +$ErrorActionPreference = 'Stop' + +$normalizedScriptDir = $ScriptDir +if ($null -eq $normalizedScriptDir) { + throw 'ScriptDir parameter is missing.' +} +$normalizedScriptDir = $normalizedScriptDir.Trim() +$normalizedScriptDir = $normalizedScriptDir.Trim('"') +if ([string]::IsNullOrWhiteSpace($normalizedScriptDir)) { + throw 'ScriptDir parameter is empty after normalization.' +} +$script:ScriptDir = [System.IO.Path]::GetFullPath($normalizedScriptDir) +$script:ToolsDir = Join-Path $script:ScriptDir 'tools' +$script:LogFile = Join-Path $script:ScriptDir 'BuildLog.txt' +$script:NugetExe = Join-Path $script:ToolsDir 'nuget.exe' +$script:NugetUrl = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' +$script:BuildToolsInstaller = Join-Path $script:ToolsDir 'vs_buildtools.exe' +$script:BuildToolsUrl = 'https://aka.ms/vs/17/release/vs_buildtools.exe' +$script:LockDir = Join-Path $script:ScriptDir '.build.lock' +$script:ProjectPath = Join-Path $script:ScriptDir 'MicroWin\MicroWin.csproj' + +function Write-LogLine { + param([string]$Message) + + $line = "[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'), $Message + Write-Host $Message + Add-Content -Path $script:LogFile -Value $line -Encoding UTF8 +} + +function Write-LogOutputLine { + param([string]$Message) + + $line = "[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'), $Message + Add-Content -Path $script:LogFile -Value $line -Encoding UTF8 +} + +function Invoke-Download { + param( + [string]$Url, + [string]$OutFile + ) + + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Write-LogLine "Downloading: $Url" + Invoke-WebRequest -Uri $Url -OutFile $OutFile + Write-LogLine "Saved: $OutFile" +} + +function Invoke-External { + param( + [string]$FilePath, + [string[]]$Arguments, + [string]$WorkingDirectory + ) + + if (-not [string]::IsNullOrWhiteSpace($WorkingDirectory)) { + Push-Location $WorkingDirectory + } + + try { + $argText = if ($Arguments) { ($Arguments -join ' ') } else { '' } + Write-LogLine "Running: $FilePath $argText".Trim() + + & $FilePath @Arguments 2>&1 | ForEach-Object { + $text = [string]$_ + Write-Host $text + Write-LogOutputLine -Message $text + } + + $exitCode = $LASTEXITCODE + if ($null -eq $exitCode) { + $exitCode = 0 + } + + if ($exitCode -ne 0) { + throw "Command failed with exit code $exitCode" + } + } + finally { + if (-not [string]::IsNullOrWhiteSpace($WorkingDirectory)) { + Pop-Location + } + } +} + +function Find-MSBuild { + $candidates = @( + 'C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe', + 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe' + ) + + foreach ($candidate in $candidates) { + if (Test-Path -Path $candidate) { + return $candidate + } + } + + $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' + if (Test-Path -Path $vswhere) { + $found = & $vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 + if ($found) { + return $found + } + } + + return $null +} + +function Find-BuiltExe { + $candidates = @( + (Join-Path $script:ScriptDir 'MicroWin\bin\Release\MicroWin.exe'), + (Join-Path $script:ScriptDir 'MicroWin\bin\AnyCPU\Release\MicroWin.exe'), + (Join-Path $script:ScriptDir 'MicroWin\bin\x64\Release\MicroWin.exe'), + (Join-Path $script:ScriptDir 'MicroWin\bin\x86\Release\MicroWin.exe') + ) + + foreach ($candidate in $candidates) { + if (Test-Path -Path $candidate) { + return $candidate + } + } + + return $null +} + +function Confirm-Yes { + param([string]$Prompt) + + $choice = Read-Host $Prompt + return ($choice -match '^(?i)y(es)?$') +} + +function Initialize-Log { + $tries = 0 + while ($tries -lt 5) { + try { + Set-Content -Path $script:LogFile -Value @( + '==================================================', + "MicroWin Build Log - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff')", + "Script: $([System.IO.Path]::Combine($script:ScriptDir, 'build.bat'))", + '==================================================', + '' + ) -Encoding UTF8 + return + } + catch { + $tries++ + Start-Sleep -Seconds 1 + } + } + + throw 'Could not initialize BuildLog.txt because it is in use.' +} + +function Acquire-Lock { + if (Test-Path -Path $script:LockDir) { + throw 'Another build process is already running. Close the other build window and try again.' + } + + New-Item -Path $script:LockDir -ItemType Directory -Force | Out-Null +} + +function Release-Lock { + if (Test-Path -Path $script:LockDir) { + Remove-Item -Path $script:LockDir -Recurse -Force -ErrorAction SilentlyContinue + } +} + +function Run-Build { + Acquire-Lock + + try { + if (-not (Test-Path -Path $script:ToolsDir)) { + New-Item -Path $script:ToolsDir -ItemType Directory | Out-Null + } + + Initialize-Log + + Write-Host '========================================' + Write-Host 'MicroWin Build Script' + Write-Host '========================================' + Write-Host '' + Write-LogLine "Build log: $script:LogFile" + + Write-Host '' + Write-LogLine '[1/4] Checking for MSBuild...' + $msbuildPath = Find-MSBuild + if (-not $msbuildPath) { + Write-LogLine 'MSBuild not found. Downloading minimal build tools...' + Invoke-Download -Url $script:BuildToolsUrl -OutFile $script:BuildToolsInstaller + + Write-LogLine 'Installing minimal build tools (MSBuild + .NET Framework 4.8 SDK)...' + Write-LogLine 'This will download ~500MB and install required components.' + Invoke-External -FilePath $script:BuildToolsInstaller -Arguments @( + '--quiet','--wait','--norestart','--nocache', + '--installPath',"$env:ProgramFiles(x86)\Microsoft Visual Studio\2022\BuildTools", + '--add','Microsoft.VisualStudio.Workload.MSBuildTools', + '--add','Microsoft.Net.Component.4.8.SDK', + '--add','Microsoft.Net.Component.4.8.TargetingPack' + ) -WorkingDirectory $script:ScriptDir + + $msbuildPath = Find-MSBuild + if (-not $msbuildPath) { + throw 'MSBuild not found after installation.' + } + } + Write-LogLine "Found MSBuild at: $msbuildPath" + + Write-Host '' + Write-LogLine '[2/4] Checking for NuGet...' + if (-not (Test-Path -Path $script:NugetExe)) { + Write-LogLine 'NuGet not found. Downloading NuGet.exe ~8MB...' + Invoke-Download -Url $script:NugetUrl -OutFile $script:NugetExe + } + Write-LogLine "Found NuGet at: $script:NugetExe" + + Write-Host '' + Write-LogLine '[3/4] Restoring NuGet packages...' + Invoke-External -FilePath $script:NugetExe -Arguments @('restore','MicroWin\MicroWin.csproj','-PackagesDirectory','packages') -WorkingDirectory $script:ScriptDir + Write-LogLine 'NuGet restore completed.' + + Write-Host '' + Write-LogLine '[4/4] Building MicroWin...' + Invoke-External -FilePath $msbuildPath -Arguments @('MicroWin\MicroWin.csproj','/p:Configuration=Release','/p:Platform=AnyCPU','/verbosity:minimal') -WorkingDirectory $script:ScriptDir + + Write-Host '' + Write-Host '========================================' + Write-Host 'Build completed successfully!' + Write-Host '========================================' + Write-Host '' + Write-LogLine 'Build completed successfully.' + + $builtExe = Find-BuiltExe + $outputDir = Join-Path $script:ScriptDir 'MicroWin\bin\Release\' + if ($builtExe) { + Write-LogLine "Output location: $builtExe" + $outputDir = [System.IO.Path]::GetDirectoryName($builtExe) + if (-not $outputDir.EndsWith('\')) { + $outputDir = "$outputDir\" + } + } + else { + Write-LogLine 'Output location: not found' + } + + Write-Host '' + $desktopBuildDir = Join-Path $env:USERPROFILE 'Desktop\MicroWin-Build' + if ($builtExe) { + if (Confirm-Yes 'Copy build output files to Desktop\MicroWin-Build? (Y/N)') { + if (Test-Path -Path $desktopBuildDir) { + Remove-Item -Path $desktopBuildDir -Recurse -Force -ErrorAction SilentlyContinue + } + New-Item -Path $desktopBuildDir -ItemType Directory -Force | Out-Null + Copy-Item -Path (Join-Path $outputDir '*') -Destination $desktopBuildDir -Recurse -Force + if (Test-Path -Path $script:LogFile) { + Copy-Item -Path $script:LogFile -Destination (Join-Path $desktopBuildDir 'BuildLog.txt') -Force + } + Write-LogLine "Copied build output to: $desktopBuildDir" + } + else { + Write-LogLine 'Desktop output copy skipped by user.' + } + } + else { + Write-LogLine 'WARNING: Built executable not found, skipping Desktop copy option.' + } + + Write-Host '' + if (Confirm-Yes 'Remove downloaded build utility files from tools folder? (Y/N)') { + if (Test-Path -Path $script:NugetExe) { Remove-Item -Path $script:NugetExe -Force -ErrorAction SilentlyContinue } + if (Test-Path -Path $script:BuildToolsInstaller) { Remove-Item -Path $script:BuildToolsInstaller -Force -ErrorAction SilentlyContinue } + if (Test-Path -Path $script:ToolsDir) { + try { Remove-Item -Path $script:ToolsDir -Force -ErrorAction Stop } catch {} + } + Write-LogLine 'Removed downloaded build utility files from tools folder.' + } + else { + Write-LogLine 'Build utility cleanup skipped by user.' + } + + Write-Host '' + if (Confirm-Yes 'Open build output folder now? (Y/N)') { + if (Test-Path -Path $outputDir) { + Start-Process -FilePath $outputDir | Out-Null + Write-LogLine "Opened output folder: $outputDir" + } + else { + Write-LogLine 'WARNING: Output folder not found.' + } + } + else { + Write-LogLine 'Open output folder skipped by user.' + } + + Write-Host '' + Write-LogLine 'Build script finished.' + exit 0 + } + catch { + Write-LogLine "ERROR: $($_.Exception.Message)" + exit 1 + } + finally { + Release-Lock + } +} + +if ($Action -eq 'Build') { + Run-Build +} diff --git a/README.md b/README.md index e6d804c..7031c08 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![License: MIT](https://img.shields.io/badge/License-MIT-green) ![Platform: Windows](https://img.shields.io/badge/Platform-Windows%2010%20%2F%2011-0078d4) +[![CI Build](https://github.com/dennisflb/MicroWin/actions/workflows/ci-build.yml/badge.svg)](https://github.com/dennisflb/MicroWin/actions/workflows/ci-build.yml) This repository contains the source code for the C# rewrite of MicroWin. This serves as the continuation of MicroWin from [WinUtil](https://github.com/ChrisTitusTech/winutil) that got removed recently. diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..964ead3 --- /dev/null +++ b/build.bat @@ -0,0 +1,33 @@ +@echo off +setlocal enabledelayedexpansion + +set "SCRIPT_DIR=%~dp0" +set "SCRIPT_DIR_SAFE=%SCRIPT_DIR%." +set "HELPER_PS1=%SCRIPT_DIR%Bhelper.ps1" + +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Relaunching with Administrator privileges... + pause + powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Start-Process -FilePath '%~f0' -WorkingDirectory '%CD%' -Verb RunAs -ErrorAction Stop; exit 0 } catch { exit 1 }" + if !errorLevel! neq 0 ( + echo ERROR: Failed to start elevated process. UAC may have been cancelled or blocked. + pause + exit /b 1 + ) + echo Elevated build started in a new window. + pause + exit /b 0 +) + +if not exist "%HELPER_PS1%" ( + echo ERROR: Missing helper script: %HELPER_PS1% + pause + exit /b 1 +) + +powershell -NoProfile -ExecutionPolicy Bypass -File "%HELPER_PS1%" -Action Build -ScriptDir "%SCRIPT_DIR_SAFE%" +set "EXIT_CODE=%errorLevel%" + +pause +exit /b %EXIT_CODE% diff --git a/microwin.zip b/microwin.zip deleted file mode 100644 index 37d3a6d..0000000 Binary files a/microwin.zip and /dev/null differ