Build AppControl Manager MSIX Package #143
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build AppControl Manager MSIX Package | |
| on: | |
| workflow_dispatch: # Only the repository's owner can initiate this workflow | |
| # Prevent the workflow from running more than once per change | |
| concurrency: | |
| group: ACMBuildWorkFlowGitHubSource | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| runs-on: windows-2025 | |
| # Create outputs for this job where only other job that use "needs" keyword can use them. This job itself cannot access them. | |
| outputs: | |
| DRAFT_RELEASE_ID: ${{ steps.find_draft_release.outputs.DRAFT_RELEASE_ID }} | |
| DRAFT_RELEASE_TAG: ${{ steps.find_draft_release.outputs.DRAFT_RELEASE_TAG }} | |
| PACKAGE_VERSION: ${{ steps.main_buildOp.outputs.PACKAGE_VERSION }} | |
| MSIXBundle_PATH: ${{ steps.main_buildOp.outputs.MSIXBundle_PATH }} | |
| MSIXBundle_NAME: ${{ steps.main_buildOp.outputs.MSIXBundle_NAME }} | |
| X64MSBuildLog_PATH: ${{ steps.main_buildOp.outputs.X64MSBuildLog_PATH }} | |
| ARM64MSBuildLog_PATH: ${{ steps.main_buildOp.outputs.ARM64MSBuildLog_PATH }} | |
| X64Symbol_PATH: ${{ steps.main_buildOp.outputs.X64Symbol_PATH }} | |
| X64Symbol_NAME: ${{ steps.main_buildOp.outputs.X64Symbol_NAME }} | |
| ARM64Symbol_PATH: ${{ steps.main_buildOp.outputs.ARM64Symbol_PATH }} | |
| ARM64Symbol_NAME: ${{ steps.main_buildOp.outputs.ARM64Symbol_NAME }} | |
| SBOM_PATH: ${{ steps.main_buildOp.outputs.SBOM_PATH }} | |
| SBOM_NAME: ${{ steps.main_buildOp.outputs.SBOM_NAME }} | |
| permissions: | |
| contents: write # Required for adding files to the GitHub draft release | |
| steps: | |
| # - name: Updating Winget | |
| # shell: pwsh | |
| # run: | | |
| # try { | |
| # Write-Host -Object 'The version of the pre-installed Winget on the Runner:' | |
| # Write-Host -Object (winget --version) | |
| # } | |
| # catch { | |
| # Write-Host -Object 'Winget is not installed.' | |
| # } | |
| # | |
| # # Retrieve the latest Winget release information | |
| # $WingetReleases = Invoke-RestMethod -Uri 'https://api.github.com/repos/microsoft/winget-cli/releases' | |
| # $LatestRelease = $WingetReleases | Select-Object -First 1 | |
| # # Direct links to the latest Winget release assets | |
| # [string]$WingetURL = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('.msixbundle') } | Select-Object -First 1 | |
| # [string]$WingetLicense = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('License1.xml') } | Select-Object -First 1 | |
| # [string]$LatestWingetReleaseDependenciesZipURL = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('DesktopAppInstaller_Dependencies.zip') } | Select-Object -First 1 | |
| # [hashtable]$Downloads = @{ | |
| # # 'Winget.msixbundle' = 'https://aka.ms/getwinget' This is updated slower than the GitHub release | |
| # 'DesktopAppInstaller_Dependencies.zip' = $LatestWingetReleaseDependenciesZipURL | |
| # 'Winget.msixbundle' = $WingetURL | |
| # 'License1.xml' = $WingetLicense | |
| # } | |
| # $Downloads.GetEnumerator() | ForEach-Object -Parallel { | |
| # Invoke-RestMethod -Uri $_.Value -OutFile $_.Key | |
| # } | |
| # | |
| # Expand-Archive -Path 'DesktopAppInstaller_Dependencies.zip' -DestinationPath .\ -Force | |
| # # Get the paths to all of the dependencies | |
| # [string[]]$DependencyPaths = (Get-ChildItem -Path .\x64 -Filter '*.appx' -File -Force).FullName | |
| # | |
| # # Required to update the Winget | |
| # Stop-Process -Name 'WindowsTerminal' -Force -ErrorAction Ignore | |
| # | |
| # Add-AppxProvisionedPackage -Online -PackagePath 'Winget.msixbundle' -DependencyPackagePath $DependencyPaths -LicensePath 'License1.xml' | |
| # | |
| # Add-AppPackage -Path 'Winget.msixbundle' -DependencyPath "$($DependencyPaths[0])", "$($DependencyPaths[1])" -ForceTargetApplicationShutdown -ForceUpdateFromAnyVersion | |
| # | |
| - name: Check out the repository code | |
| uses: actions/checkout@v5 | |
| - name: Building And Packaging the AppControl Manager | |
| id: main_buildOp | |
| shell: pwsh | |
| env: | |
| PARTNERCENTER_APPLICATIONID: ${{ secrets.PARTNERCENTER_APPLICATIONID }} | |
| PARTNERCENTER_CLIENTID: ${{ secrets.PARTNERCENTER_CLIENTID }} | |
| PARTNERCENTER_CLIENTSECRET: ${{ secrets.PARTNERCENTER_CLIENTSECRET }} | |
| PARTNERCENTER_TOKENENDPOINT: ${{ secrets.PARTNERCENTER_TOKENENDPOINT }} | |
| working-directory: './AppControl Manager' # Setting up working directory to ensure dotnet build will see the global.json file in the "AppControl Manager" sub-directory | |
| run: | | |
| # Requires -Version 5.1 | |
| # Requires -RunAsAdministrator | |
| function Build_ACM { | |
| param( | |
| [ValidateSet('Store', 'Self')] | |
| [string]$Type, | |
| [bool]$DownloadRepo, | |
| [bool]$InstallDeps, | |
| [bool]$Workflow, | |
| [bool]$UpdateWorkLoads, | |
| [bool]$Install, | |
| [bool]$Upload, | |
| [bool]$X64ONLY | |
| ) | |
| [string]$PackageFamilyName = '' | |
| [string]$PackageHashAlgo = '' | |
| [string]$PackagePublisher = '' | |
| [string]$PackageName = '' | |
| [string]$PackagePhoneProductId = '' | |
| [string]$PackagePhonePublisherId = '' | |
| [string]$PackagePublisherDisplayName = '' | |
| Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 -Force | |
| if ($Type -eq 'Store') { | |
| $PackageFamilyName = 'VioletHansen.AppControlManager_ea7andspwdn10' | |
| $PackageHashAlgo = 'SHA256' | |
| $PackagePublisher = 'CN=C62E63B6-6EF1-4F86-B80F-41A725BD0189' | |
| $PackageName = 'VioletHansen.AppControlManager' | |
| $PackagePhoneProductId = '4157a676-f4c2-4a8c-a511-b7fb2255c6f5' | |
| $PackagePhonePublisherId = '387464d6-cb95-4e5f-9c8f-f153a4855fb2' | |
| $PackagePublisherDisplayName = 'Violet Hansen' | |
| } | |
| else { | |
| $PackageFamilyName = 'AppControlManager_sadt7br7jpt02' | |
| $PackageHashAlgo = 'SHA512' | |
| $PackagePublisher = 'CN=SelfSignedCertForAppControlManager' | |
| $PackageName = 'AppControlManager' | |
| $PackagePhoneProductId = '199a23ec-7cb6-4ab5-ab50-8baca348bc79' | |
| $PackagePhonePublisherId = '00000000-0000-0000-0000-000000000000' | |
| $PackagePublisherDisplayName = 'SelfSignedCertForAppControlManager' | |
| } | |
| $ErrorActionPreference = 'Stop' | |
| # Start the stopwatch | |
| $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() | |
| [System.String]$script:AppControlManagerDirectory | |
| if ($DownloadRepo) { | |
| [System.String]$BranchName = 'main' | |
| [System.String]$RepoName = 'Harden-Windows-Security' | |
| [System.String]$RepoUrl = "https://github.com/HotCakeX/$RepoName/archive/refs/heads/$BranchName.zip" | |
| [System.String]$ZipPath = [System.IO.Path]::Combine($env:TEMP, "$RepoName.zip") | |
| [System.String]$InitialWorkingDirectory = $PWD.Path | |
| $script:AppControlManagerDirectory = [System.IO.Path]::Combine($InitialWorkingDirectory, "$RepoName-$BranchName", 'AppControl Manager') | |
| if (Test-Path -Path $script:AppControlManagerDirectory -PathType Container) { | |
| Remove-Item -Path $script:AppControlManagerDirectory -Recurse -Force | |
| } | |
| Invoke-WebRequest -Uri $RepoUrl -OutFile $ZipPath | |
| Expand-Archive -Path $ZipPath -DestinationPath $InitialWorkingDirectory -Force | |
| Remove-Item -Path $ZipPath -Force | |
| Set-Location -Path $script:AppControlManagerDirectory | |
| } | |
| else { | |
| $script:AppControlManagerDirectory = $PWD.Path | |
| } | |
| if ($InstallDeps) { | |
| # Install Winget if it doesn't exist | |
| if (!(Get-Command -Name 'winget.exe' -ErrorAction Ignore)) { | |
| # Retrieve the latest Winget release information | |
| $WingetReleases = Invoke-RestMethod -Uri 'https://api.github.com/repos/microsoft/winget-cli/releases' | |
| $LatestRelease = $WingetReleases | Select-Object -First 1 | |
| # Direct links to the latest Winget release assets | |
| [string]$WingetURL = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('.msixbundle') } | Select-Object -First 1 | |
| [string]$WingetLicense = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('License1.xml') } | Select-Object -First 1 | |
| [string]$LatestWingetReleaseDependenciesZipURL = $LatestRelease.assets.browser_download_url | Where-Object -FilterScript { $_.EndsWith('DesktopAppInstaller_Dependencies.zip') } | Select-Object -First 1 | |
| [hashtable]$Downloads = @{ | |
| # 'Winget.msixbundle' = 'https://aka.ms/getwinget' This is updated slower than the GitHub release | |
| 'DesktopAppInstaller_Dependencies.zip' = $LatestWingetReleaseDependenciesZipURL | |
| 'Winget.msixbundle' = $WingetURL | |
| 'License1.xml' = $WingetLicense | |
| } | |
| $Downloads.GetEnumerator() | ForEach-Object -Parallel { | |
| Invoke-RestMethod -Uri $_.Value -OutFile $_.Key | |
| } | |
| Expand-Archive -Path 'DesktopAppInstaller_Dependencies.zip' -DestinationPath .\ -Force | |
| # Required to update the Winget | |
| Stop-Process -Name 'WindowsTerminal' -Force -ErrorAction Ignore | |
| # Get the paths to all of the dependencies | |
| [string[]]$DependencyPaths = (Get-ChildItem -Path .\x64 -Filter '*.appx' -File -Force).FullName | |
| Add-AppxProvisionedPackage -Online -PackagePath 'Winget.msixbundle' -DependencyPackagePath $DependencyPaths -LicensePath 'License1.xml' | |
| Add-AppPackage -Path 'Winget.msixbundle' -DependencyPath "$($DependencyPaths[0])", "$($DependencyPaths[1])" -ForceTargetApplicationShutdown -ForceUpdateFromAnyVersion | |
| } | |
| Write-Host -Object 'The version of the Winget currently in use:' | |
| Write-Host -Object (winget --version) | |
| winget source update | |
| Write-Host -Object "`nInstalling Rust toolchain" -ForegroundColor Magenta | |
| $null = winget install --id Rustlang.Rustup --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed to install the Rust toolchain: $LASTEXITCODE") } | |
| Write-Host -Object "`nInstalling .NET SDK" -ForegroundColor Magenta | |
| $null = winget install --id Microsoft.DotNet.SDK.Preview --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed to install .NET SDK: $LASTEXITCODE") } | |
| Write-Host -Object "`nInstalling Visual Studio Build Tools" -ForegroundColor Magenta | |
| # Downloads the online installer and automatically runs it and installs the build tools | |
| # https://learn.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment | |
| # https://learn.microsoft.com/visualstudio/install/workload-component-id-vs-build-tools | |
| # https://learn.microsoft.com/visualstudio/install/use-command-line-parameters-to-install-visual-studio | |
| # https://learn.microsoft.com/visualstudio/install/workload-component-id-vs-community | |
| # winget install --id Microsoft.VisualStudio.2022.BuildTools --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget --override '--force --wait --passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Workload.UniversalBuildTools --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended --add Microsoft.VisualStudio.Component.VC.Tools.ARM64' | |
| # Using this until version 18 build tools are added to Winget | |
| Invoke-RestMethod -Uri "https://aka.ms/vs/18/insiders/vs_BuildTools.exe" -OutFile "vs_BuildTools.exe" | |
| Start-Process -Wait -FilePath .\vs_BuildTools.exe -ArgumentList '--force --wait --passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Workload.UniversalBuildTools --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended --add Microsoft.VisualStudio.Component.VC.Tools.ARM64' | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New('Failed to install Visual Studio Build Tools') } | |
| # winget install --id Microsoft.VCRedist.2015+.x64 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget | |
| } | |
| # Refresh the environment variables so the current session detects the new dotnet installation | |
| $Env:Path = [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) + ';' + | |
| [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::User) | |
| # https://github.com/Microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell | |
| $installationPath = . 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -latest -property installationPath | |
| if ($installationPath -and (Test-Path -Path "$installationPath\Common7\Tools\vsdevcmd.bat" -PathType Leaf)) { | |
| & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -no_logo && set" | ForEach-Object -Process { | |
| $name, $value = $_ -split '=', 2 | |
| Set-Content -Path env:\"$name" -Value $value -Force | |
| Write-Host -Object "Setting environment variable: $name=$value" | |
| } | |
| } | |
| # Remove any possible existing directories | |
| Remove-Item -Path .\MSIXOutputX64 -Recurse -Force -ErrorAction Ignore | |
| Remove-Item -Path .\MSIXOutputARM64 -Recurse -Force -ErrorAction Ignore | |
| Remove-Item -Path .\MSIXBundleOutput -Recurse -Force -ErrorAction Ignore | |
| Remove-Item -Path .\bin -Recurse -Force -ErrorAction Ignore | |
| Remove-Item -Path .\obj -Recurse -Force -ErrorAction Ignore | |
| if ($UpdateWorkLoads) { | |
| # Update the workloads | |
| dotnet workload update | |
| dotnet workload config --update-mode workload-set | |
| dotnet workload update | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed updating the workloads. Exit Code: $LASTEXITCODE") } | |
| } | |
| Write-Host -Object "`nChecking .NET info`n`n" -ForegroundColor Magenta | |
| dotnet --info | |
| Write-Host -Object "`nListing installed .NET SDKs`n`n" -ForegroundColor Magenta | |
| dotnet --list-sdks | |
| function Find-mspdbcmf { | |
| # "-products *" is necessary to detect BuildTools too | |
| [string]$VisualStudioPath = . 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -latest -property resolvedInstallationPath -products * | |
| [string]$BasePath = [System.IO.Path]::Combine($VisualStudioPath, 'VC', 'Tools', 'MSVC') | |
| # Get all subdirectories under the base path | |
| [System.String[]]$VersionDirs = [System.IO.Directory]::GetDirectories($BasePath) | |
| # Initialize the highest version with a minimal version value. | |
| [System.Version]$HighestVersion = [System.Version]::New('0.0.0.0') | |
| [System.String]$HighestVersionFolder = $null | |
| # Loop through each directory to find the highest version folder. | |
| foreach ($Dir in $VersionDirs) { | |
| # Extract the folder name | |
| [System.String]$FolderName = [System.IO.Path]::GetFileName($Dir) | |
| [System.Version]$CurrentVersion = $null | |
| # Try parsing the folder name as a Version. | |
| if ([System.Version]::TryParse($FolderName, [ref] $CurrentVersion)) { | |
| # Compare versions | |
| if ($CurrentVersion.CompareTo($HighestVersion) -gt 0) { | |
| $HighestVersion = $CurrentVersion | |
| $HighestVersionFolder = $FolderName | |
| } | |
| } | |
| } | |
| # If no valid version folder is found | |
| if (!$HighestVersionFolder) { | |
| throw [System.IO.DirectoryNotFoundException]::New("No valid version directories found in $BasePath") | |
| } | |
| # Combine the base path, the highest version folder, the architecture folder, and the file name. | |
| [System.String]$mspdbcmfPath = [System.IO.Path]::Combine($BasePath, $HighestVersionFolder, 'bin', 'Hostx64', 'x64', 'mspdbcmf.exe') | |
| if (![System.IO.File]::Exists($mspdbcmfPath)) { | |
| throw [System.IO.FileNotFoundException]::New("mspdbcmf.exe not found at $mspdbcmfPath") | |
| } | |
| return $mspdbcmfPath | |
| } | |
| [string]$mspdbcmfPath = Find-mspdbcmf | |
| function Find-MSBuild { | |
| # "-products *" is necessary to detect BuildTools too | |
| [string]$VisualStudioPath = . 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -latest -property resolvedInstallationPath -products * | |
| [string]$MSBuildPath = [System.IO.Path]::Combine($VisualStudioPath, 'MSBuild', 'Current', 'Bin', 'MSBuild.exe') | |
| if (![System.IO.File]::Exists($MSBuildPath)) { | |
| throw [System.IO.FileNotFoundException]::New("MSBuild.exe not found at $MSBuildPath") | |
| } | |
| return $MSBuildPath | |
| } | |
| [string]$MSBuildPath = Find-MSBuild | |
| #region --- Compile C++ projects --- | |
| ### ComManager | |
| . $MSBuildPath 'eXclude\ComManager\ComManager.slnx' /p:Configuration=Release /p:Platform=x64 /target:"clean;Rebuild" | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ComManager solution for X64. Exit Code: $LASTEXITCODE") } | |
| if (!$X64ONLY) { | |
| . $MSBuildPath 'eXclude\ComManager\ComManager.slnx' /p:Configuration=Release /p:Platform=arm64 /target:"clean;Rebuild" | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ComManager solution for ARM64. Exit Code: $LASTEXITCODE") } | |
| } | |
| ### Shell | |
| [string]$newPFN = "$PackageFamilyName!App" | |
| [string]$content = Get-Content 'eXclude\Shell\Shell.cpp' -Raw | |
| [string]$content = $content -replace 'static constexpr LPCWSTR APP_CONTROL_MANAGER_PFN = L"[^"]*";', "static constexpr LPCWSTR APP_CONTROL_MANAGER_PFN = L`"$newPFN`";" | |
| $content | Set-Content 'eXclude\Shell\Shell.cpp' -NoNewline -Force | |
| . $MSBuildPath 'eXclude\Shell\Shell.slnx' /p:Configuration=Release /p:Platform=x64 /target:"clean;Rebuild" | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building the Shell solution for X64. Exit Code: $LASTEXITCODE") } | |
| if (!$X64ONLY) { | |
| . $MSBuildPath 'eXclude\Shell\Shell.slnx' /p:Configuration=Release /p:Platform=arm64 /target:"clean;Rebuild" | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building the Shell solution for ARM64. Exit Code: $LASTEXITCODE") } | |
| } | |
| #endregion | |
| #region --- RUST projects --- | |
| # Uncomment this once stable toolchain supports ehcont security feature, till then we use nightly only | |
| # rustup default stable | |
| rustup default nightly | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed setting Rust toolchain to Stable. Exit Code: $LASTEXITCODE") } | |
| if (!$X64ONLY) { | |
| rustup target add aarch64-pc-windows-msvc | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed adding aarch64-pc-windows-msvc target to Rust toolchain. Exit Code: $LASTEXITCODE") } | |
| } | |
| rustup target add x86_64-pc-windows-msvc | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed adding x86_64-pc-windows-msvc target to Rust toolchain. Exit Code: $LASTEXITCODE") } | |
| rustup update | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed updating Rust. Exit Code: $LASTEXITCODE") } | |
| cargo version | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed checking for Rust version. Exit Code: $LASTEXITCODE") } | |
| [string]$Current_Location = (Get-Location).Path | |
| Set-Location -Path '.\eXclude\Rust Interop Library' | |
| if (Test-Path -PathType Leaf -LiteralPath 'Cargo.lock') { | |
| Remove-Item -Force -LiteralPath 'Cargo.lock' | |
| } | |
| rustup toolchain install nightly | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed installing nightly Rust toolchain. Exit Code: $LASTEXITCODE") } | |
| rustup default nightly | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed setting Rust toolchain to Nightly. Exit Code: $LASTEXITCODE") } | |
| rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed adding rust-src component to Nightly toolchain. Exit Code: $LASTEXITCODE") } | |
| rustup update | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed updating Rust. Exit Code: $LASTEXITCODE") } | |
| cargo version | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed checking for Rust version. Exit Code: $LASTEXITCODE") } | |
| cargo clean | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed cleaning the Rust project. Exit Code: $LASTEXITCODE") } | |
| cargo update --verbose | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed updating Rust. Exit Code: $LASTEXITCODE") } | |
| cargo tree | |
| rustup show active-toolchain | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed showing active Rust toolchain. Exit Code: $LASTEXITCODE") } | |
| cargo build_x64 | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building x64 Rust Interop project. Exit Code: $LASTEXITCODE") } | |
| if (!$X64ONLY) { | |
| cargo build_arm64 | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ARM64 Rust Interop project. Exit Code: $LASTEXITCODE") } | |
| } | |
| Set-Location -Path $Current_Location | |
| #endregion | |
| #region XML Modifications | |
| [string]$CsProjFilePath = (Resolve-Path -Path '.\AppControl Manager.csproj').Path | |
| [string]$AppxManifestFilePath = (Resolve-Path -Path '.\Package.appxmanifest').Path | |
| # Adjust the Digest Algorithm based on the package source | |
| [xml]$ProjXMLContent = Get-Content -Path $CsProjFilePath -Force | |
| # Grab ALL existing nodes, wherever they are | |
| $nodes = $ProjXMLContent.SelectNodes('//AppxPackageSigningTimestampDigestAlgorithm') | |
| foreach ($node in $nodes) { | |
| $node.InnerText = $PackageHashAlgo | |
| } | |
| $ProjXMLContent.Save($CsProjFilePath) | |
| # Configure the Package Manifest Dits | |
| [xml]$AppxManifestContent = Get-Content -Path $AppxManifestFilePath -Force | |
| $ns = New-Object System.Xml.XmlNamespaceManager($AppxManifestContent.NameTable) | |
| $ns.AddNamespace('ns', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10') | |
| $ns.AddNamespace('mp', 'http://schemas.microsoft.com/appx/2014/phone/manifest') | |
| # Update the <Identity> attributes | |
| $identity = $AppxManifestContent.SelectSingleNode('/ns:Package/ns:Identity', $ns) | |
| $identity.SetAttribute('Name', $PackageName) | |
| $identity.SetAttribute('Publisher', $PackagePublisher) | |
| # Update the <mp:PhoneIdentity> attributes | |
| $phoneId = $AppxManifestContent.SelectSingleNode('/ns:Package/mp:PhoneIdentity', $ns) | |
| $phoneId.SetAttribute('PhoneProductId', $PackagePhoneProductId) | |
| $phoneId.SetAttribute('PhonePublisherId', $PackagePhonePublisherId) | |
| # Update the <PublisherDisplayName> element | |
| $pubDisplay = $AppxManifestContent.SelectSingleNode('/ns:Package/ns:Properties/ns:PublisherDisplayName', $ns) | |
| $pubDisplay.InnerText = $PackagePublisherDisplayName | |
| $AppxManifestContent.Save($AppxManifestFilePath) | |
| #endregion XML Modifications | |
| # https://learn.microsoft.com/dotnet/core/tools/dotnet-build | |
| # https://learn.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference | |
| # https://learn.microsoft.com/visualstudio/msbuild/common-msbuild-project-properties | |
| # Copy the X64 components to the directory before the build starts | |
| Copy-Item -Path '.\eXclude\Shell\x64\Release\Shell.dll' -Destination 'Shell' -Force | |
| Copy-Item -Path '.\eXclude\ComManager\x64\Release\ComManager.exe' -Destination '.\CppInterop\ComManager.exe' -Force | |
| # Generate for X64 architecture | |
| dotnet clean 'AppControl Manager.csproj' --configuration Release | |
| dotnet build 'AppControl Manager.csproj' --configuration Release --verbosity minimal /p:Platform=x64 /p:RuntimeIdentifier=win-x64 | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building x64 AppControl Manager project. Exit Code: $LASTEXITCODE") } | |
| dotnet msbuild 'AppControl Manager.csproj' /t:Publish /p:Configuration=Release /p:RuntimeIdentifier=win-x64 /p:AppxPackageDir="MSIXOutputX64\" /p:GenerateAppxPackageOnBuild=true /p:Platform=x64 -v:minimal /p:MsPdbCmfExeFullpath=$mspdbcmfPath -bl:X64MSBuildLog.binlog | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed packaging x64 AppControl Manager project. Exit Code: $LASTEXITCODE") } | |
| if (!$X64ONLY) { | |
| # Copy the ARM64 components to the directory before the build starts | |
| Copy-Item -Path '.\eXclude\Shell\ARM64\Release\Shell.dll' -Destination 'Shell' -Force | |
| Copy-Item -Path '.\eXclude\ComManager\ARM64\Release\ComManager.exe' -Destination '.\CppInterop\ComManager.exe' -Force | |
| # Generate for ARM64 architecture | |
| dotnet clean 'AppControl Manager.csproj' --configuration Release | |
| dotnet build 'AppControl Manager.csproj' --configuration Release --verbosity minimal /p:Platform=ARM64 /p:RuntimeIdentifier=win-arm64 | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ARM64 AppControl Manager project. Exit Code: $LASTEXITCODE") } | |
| dotnet msbuild 'AppControl Manager.csproj' /t:Publish /p:Configuration=Release /p:RuntimeIdentifier=win-arm64 /p:AppxPackageDir="MSIXOutputARM64\" /p:GenerateAppxPackageOnBuild=true /p:Platform=ARM64 -v:minimal /p:MsPdbCmfExeFullpath=$mspdbcmfPath -bl:ARM64MSBuildLog.binlog | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed packaging ARM64 AppControl Manager project. Exit Code: $LASTEXITCODE") } | |
| } | |
| function Get-MSIXFile { | |
| param( | |
| [System.String]$BasePath, | |
| [System.String]$FolderPattern, | |
| [System.String]$FileNamePattern, | |
| [System.String]$ErrorMessageFolder, | |
| [System.String]$ErrorMessageFile | |
| ) | |
| # Get all subdirectories in the base path matching the folder pattern | |
| [System.String[]]$Folders = [System.IO.Directory]::GetDirectories($BasePath) | |
| [System.String]$DetectedFolder = $null | |
| foreach ($Folder in $Folders) { | |
| if ([System.Text.RegularExpressions.Regex]::IsMatch($Folder, $FolderPattern)) { | |
| $DetectedFolder = $Folder | |
| break | |
| } | |
| } | |
| if (!$DetectedFolder) { | |
| throw [System.InvalidOperationException]::New($ErrorMessageFolder) | |
| } | |
| # Get the full path of the first file matching the file name pattern inside the found folder | |
| [System.String[]]$Files = [System.IO.Directory]::GetFiles($DetectedFolder) | |
| [System.String]$DetectedFile = $null | |
| foreach ($File in $Files) { | |
| if ([System.Text.RegularExpressions.Regex]::IsMatch($File, $FileNamePattern)) { | |
| $DetectedFile = $File | |
| break | |
| } | |
| } | |
| if (!$DetectedFile) { | |
| throw [System.InvalidOperationException]::New($ErrorMessageFile) | |
| } | |
| return $DetectedFile | |
| } | |
| #region Finding X64 outputs | |
| [System.String]$FinalMSIXX64Path = Get-MSIXFile -BasePath ([System.IO.Path]::Combine($PWD.Path, 'MSIXOutputX64')) -FolderPattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_x64_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_x64\.msix' -ErrorMessageFolder 'Could not find the directory for X64 MSIX file' -ErrorMessageFile 'Could not find the X64 MSIX file' | |
| [System.String]$FinalMSIXX64Name = [System.IO.Path]::GetFileName($FinalMSIXX64Path) | |
| [System.String]$FinalMSIXX64SymbolPath = Get-MSIXFile -BasePath ([System.IO.Path]::Combine($PWD.Path, 'MSIXOutputX64')) -FolderPattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_x64_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_x64\.appxsym' -ErrorMessageFolder 'Could not find the directory for X64 symbol file' -ErrorMessageFile 'Could not find the X64 symbol file' | |
| [System.String]$FinalMSIXX64SymbolName = [System.IO.Path]::GetFileName($FinalMSIXX64SymbolPath) | |
| #endregion | |
| if (!$X64ONLY) { | |
| #region Finding ARM64 outputs | |
| [System.String]$FinalMSIXARM64Path = Get-MSIXFile -BasePath ([System.IO.Path]::Combine($PWD.Path, 'MSIXOutputARM64')) -FolderPattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_arm64_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_arm64\.msix' -ErrorMessageFolder 'Could not find the directory for ARM64 MSIX file' -ErrorMessageFile 'Could not find the ARM64 MSIX file' | |
| [System.String]$FinalMSIXARM64Name = [System.IO.Path]::GetFileName($FinalMSIXARM64Path) | |
| [System.String]$FinalMSIXARM64SymbolPath = Get-MSIXFile -BasePath ([System.IO.Path]::Combine($PWD.Path, 'MSIXOutputARM64')) -FolderPattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_arm64_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_arm64\.appxsym' -ErrorMessageFolder 'Could not find the directory for ARM64 symbol file' -ErrorMessageFile 'Could not find the ARM64 symbol file' | |
| [System.String]$FinalMSIXARM64SymbolName = [System.IO.Path]::GetFileName($FinalMSIXARM64SymbolPath) | |
| #endregion | |
| } | |
| #region Detect and Validate File Versions | |
| [System.Text.RegularExpressions.Regex]$versionRegexX64 = [System.Text.RegularExpressions.Regex]::New('AppControl Manager_(\d+\.\d+\.\d+\.\d+)_x64\.msix') | |
| if (!$X64ONLY) { | |
| [System.Text.RegularExpressions.Regex]$versionRegexARM64 = [System.Text.RegularExpressions.Regex]::New('AppControl Manager_(\d+\.\d+\.\d+\.\d+)_arm64\.msix') | |
| } | |
| [System.Text.RegularExpressions.Match]$MatchX64 = $versionRegexX64.Match($FinalMSIXX64Name) | |
| if (!$X64ONLY) { | |
| [System.Text.RegularExpressions.Match]$MatchARM64 = $versionRegexARM64.Match($FinalMSIXARM64Name) | |
| } | |
| if (!$MatchX64.Success) { | |
| throw [System.InvalidOperationException]::New('Could not detect version from X64 file name') | |
| } | |
| if (!$X64ONLY) { | |
| if (!$MatchARM64.Success) { | |
| throw [System.InvalidOperationException]::New('Could not detect version from ARM64 file name') | |
| } | |
| } | |
| [System.String]$versionX64 = $MatchX64.Groups[1].Value | |
| if (!$X64ONLY) { | |
| [System.String]$versionARM64 = $MatchARM64.Groups[1].Value | |
| if ($versionX64 -ne $versionARM64) { | |
| throw [System.InvalidOperationException]::New('The versions in X64 and ARM64 files do not match') | |
| } | |
| } | |
| # Craft the file name for the MSIX Bundle file | |
| [System.String]$FinalBundleFileName = "AppControl Manager_$versionX64.msixbundle" | |
| #endregion | |
| # Creating the directory where the MSIX packages will be copied to | |
| [System.String]$MSIXBundleOutput = [System.IO.Directory]::CreateDirectory([System.IO.Path]::Combine($script:AppControlManagerDirectory, 'MSIXBundleOutput')).FullName | |
| [System.IO.File]::Copy($FinalMSIXX64Path, [System.IO.Path]::Combine($MSIXBundleOutput, $FinalMSIXX64Name), $true) | |
| if (!$X64ONLY) { | |
| [System.IO.File]::Copy($FinalMSIXARM64Path, [System.IO.Path]::Combine($MSIXBundleOutput, $FinalMSIXARM64Name), $true) | |
| } | |
| # The path to the final MSIX Bundle file | |
| [System.String]$MSIXBundle = [System.IO.Path]::Combine($MSIXBundleOutput, $FinalBundleFileName) | |
| function Get-MakeAppxPath { | |
| [System.String]$BasePath = 'C:\Program Files (x86)\Windows Kits\10\bin' | |
| # Get all subdirectories under the base path | |
| [System.String[]]$VersionDirs = [System.IO.Directory]::GetDirectories($BasePath) | |
| # Initialize the highest version with a minimal version value. | |
| [System.Version]$HighestVersion = [System.Version]::New('0.0.0.0') | |
| [System.String]$HighestVersionFolder = $null | |
| # Loop through each directory to find the highest version folder. | |
| foreach ($Dir in $VersionDirs) { | |
| # Extract the folder name | |
| [System.String]$FolderName = [System.IO.Path]::GetFileName($Dir) | |
| [System.Version]$CurrentVersion = $null | |
| # Try parsing the folder name as a Version. | |
| if ([System.Version]::TryParse($FolderName, [ref] $CurrentVersion)) { | |
| # Compare versions | |
| if ($CurrentVersion.CompareTo($HighestVersion) -gt 0) { | |
| $HighestVersion = $CurrentVersion | |
| $HighestVersionFolder = $FolderName | |
| } | |
| } | |
| } | |
| # If no valid version folder is found | |
| if (!$HighestVersionFolder) { | |
| throw [System.IO.DirectoryNotFoundException]::New("No valid version directories found in $BasePath") | |
| } | |
| [string]$CPUArch = @{AMD64 = 'x64'; ARM64 = 'arm64' }[$Env:PROCESSOR_ARCHITECTURE] | |
| if ([System.String]::IsNullOrWhiteSpace($CPUArch)) { throw [System.PlatformNotSupportedException]::New('Only AMD64 and ARM64 architectures are supported.') } | |
| # Combine the base path, the highest version folder, the architecture folder, and the file name. | |
| [System.String]$MakeAppxPath = [System.IO.Path]::Combine($BasePath, $HighestVersionFolder, $CPUArch, 'makeappx.exe') | |
| return $MakeAppxPath | |
| } | |
| [System.String]$MakeAppxPath = Get-MakeAppxPath | |
| if ([System.string]::IsNullOrWhiteSpace($MakeAppxPath)) { | |
| throw [System.IO.FileNotFoundException]::New('Could not find the makeappx.exe') | |
| } | |
| # https://learn.microsoft.com/windows/win32/appxpkg/make-appx-package--makeappx-exe-#to-create-a-package-bundle-using-a-directory-structure | |
| . $MakeAppxPath bundle /d $MSIXBundleOutput /p $MSIXBundle /o /v | |
| if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("MakeAppx failed creating the MSIXBundle. Exit Code: $LASTEXITCODE") } | |
| #Endregion | |
| Write-Host -Object "X64 MSIX File Path: $FinalMSIXX64Path" -ForegroundColor Green | |
| Write-Host -Object "X64 MSIX File Name: $FinalMSIXX64Name" -ForegroundColor Green | |
| Write-Host -Object "X64 Symbols: $FinalMSIXX64SymbolPath" -ForegroundColor Green | |
| if (!$X64ONLY) { | |
| Write-Host -Object "ARM64 MSIX File Path: $FinalMSIXARM64Path" -ForegroundColor Cyan | |
| Write-Host -Object "ARM64 MSIX File Name: $FinalMSIXARM64Name" -ForegroundColor Cyan | |
| Write-Host -Object "ARM64 Symbols: $FinalMSIXARM64SymbolPath" -ForegroundColor Cyan | |
| } | |
| Write-Host -Object "MSIX Bundle File Path: $MSIXBundle" -ForegroundColor Yellow | |
| Write-Host -Object "MSIX Bundle File Name: $FinalBundleFileName" -ForegroundColor Yellow | |
| if ($Workflow) { | |
| [XML]$CSProjXMLContent = Get-Content -Path $CsProjFilePath -Force | |
| [string]$MSIXVersion = $CSProjXMLContent.Project.PropertyGroup.FileVersion | |
| [string]$MSIXVersion = $MSIXVersion.Trim() # It would have trailing whitespaces | |
| if ([string]::IsNullOrWhiteSpace($FinalMSIXX64Path) -or [string]::IsNullOrWhiteSpace($FinalMSIXX64Name) -or [string]::IsNullOrWhiteSpace($MSIXVersion)) { throw 'Necessary info could not be found' } | |
| # Write the MSIXVersion to GITHUB_ENV to set it as an environment variable for the entire workflow | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "PACKAGE_VERSION=$MSIXVersion" | |
| # Saving the details for the MSIX Bundle file | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "MSIXBundle_PATH=$MSIXBundle" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "MSIXBundle_NAME=$FinalBundleFileName" | |
| # Saving the details of the log files | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "X64MSBuildLog_PATH=$((Resolve-Path -Path .\X64MSBuildLog.binlog).Path)" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "ARM64MSBuildLog_PATH=$((Resolve-Path -Path .\ARM64MSBuildLog.binlog).Path)" | |
| # Saving the details of the X64 symbol file | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "X64Symbol_PATH=$FinalMSIXX64SymbolPath" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "X64Symbol_NAME=$FinalMSIXX64SymbolName" | |
| # Saving the details of the ARM64 symbol file | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "ARM64Symbol_PATH=$FinalMSIXARM64SymbolPath" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "ARM64Symbol_NAME=$FinalMSIXARM64SymbolName" | |
| # https://github.com/microsoft/sbom-tool | |
| # Generating SBOM | |
| Invoke-WebRequest -Uri 'https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-win-x64.exe' -OutFile "${Env:RUNNER_TEMP}\sbom-tool.exe" | |
| # https://github.com/microsoft/sbom-tool/blob/main/docs/sbom-tool-arguments.md | |
| . "${Env:RUNNER_TEMP}\sbom-tool.exe" generate -b $MSIXBundleOutput -bc .\ -pn 'AppControl Manager' -ps 'Violet Hansen' -pv $MSIXVersion -nsb 'https://github.com/HotCakeX/Harden-Windows-Security' -V Verbose -gt true -li true -pm true -D true -lto 80 | |
| # Saving the details of the SBOM file | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "SBOM_PATH=$MSIXBundleOutput/_manifest/spdx_2.2/manifest.spdx.json" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value 'SBOM_NAME=manifest.spdx.json' | |
| } | |
| if ($Install -and $PackageFamilyName -eq 'AppControlManager_sadt7br7jpt02') { | |
| (Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Harden-Windows-Security.ps1') + "AppControl -Verbose -MSIXBundlePath '$MSIXBundle'" | Invoke-Expression | |
| } | |
| if ($Upload) { | |
| dotnet clean '.\eXclude\PartnerCenter\PartnerCenter.slnx' --configuration Release | |
| dotnet build '.\eXclude\PartnerCenter\PartnerCenter.slnx' --configuration Release --verbosity minimal | |
| dotnet msbuild '.\eXclude\PartnerCenter\PartnerCenter.slnx' /p:Configuration=Release /p:Platform=x64 /p:PublishProfile=win-x64 /t:Publish -v:minimal | |
| [System.String]$TokenEndpoint = $env:PARTNERCENTER_TOKENENDPOINT | |
| [System.String]$ClientId = $env:PARTNERCENTER_CLIENTID | |
| [System.String]$ClientSecret = $env:PARTNERCENTER_CLIENTSECRET | |
| [System.String]$ApplicationId = $env:PARTNERCENTER_APPLICATIONID | |
| [System.String]$PackageFilePath = $MSIXBundle | |
| [System.String]$ReleaseNotesFilePath = (Resolve-Path -Path ReleaseNotes.txt).Path | |
| . '.\eXclude\PartnerCenter\X64Output\PartnerCenter.exe' $TokenEndpoint $ClientId $ClientSecret $ApplicationId $PackageFilePath $ReleaseNotesFilePath | |
| } | |
| if ($null -ne $Stopwatch) { | |
| $Stopwatch.Stop() | |
| $Elapsed = $Stopwatch.Elapsed | |
| [string]$Result = @" | |
| Execution Time: | |
| ---------------------------- | |
| Total Time : $($Elapsed.ToString('g')) | |
| Hours : $($Elapsed.Hours) | |
| Minutes : $($Elapsed.Minutes) | |
| Seconds : $($Elapsed.Seconds) | |
| Milliseconds : $($Elapsed.Milliseconds) | |
| ---------------------------- | |
| "@ | |
| Write-Host -Object $Result -ForegroundColor Cyan | |
| } | |
| } | |
| # For GitHub workflow 1 | |
| Build_ACM -Type Self -DownloadRepo $false -InstallDeps $true -Workflow $true -UpdateWorkLoads $false -Install $false -Upload $false -X64ONLY $false | |
| # For GitHub workflow 2 | |
| # Build_ACM -Type Store -DownloadRepo $false -InstallDeps $true -Workflow $true -UpdateWorkLoads $false -Install $false -Upload $true -X64ONLY $false | |
| # Example of building the app from the source code and installing it on a clean system with self-signed certificate | |
| # Build_ACM -Type Self -DownloadRepo $true -InstallDeps $true -Workflow $false -UpdateWorkLoads $false -Install $true -Upload $false -X64ONLY $false | |
| # Example of building the app from the source code on a clean system and uploading it to the Partner Center | |
| # Build_ACM -Type Store -DownloadRepo $true -InstallDeps $true -Workflow $false -UpdateWorkLoads $false -Install $false -Upload $true -X64ONLY $false | |
| # Local Test - X64 only | |
| # Build_ACM -Type Self -DownloadRepo $false -InstallDeps $false -Workflow $false -UpdateWorkLoads $false -Install $false -Upload $false -X64ONLY $true | |
| # Local Test - ARM64 + X64 | |
| # Build_ACM -Type Self -DownloadRepo $false -InstallDeps $false -Workflow $false -UpdateWorkLoads $false -Install $false -Upload $false -X64ONLY $false | |
| - name: Finding the Latest Draft Release | |
| id: find_draft_release | |
| shell: pwsh | |
| run: | | |
| # Find the latest draft release via GitHub REST API | |
| $Response = Invoke-RestMethod -Uri "https://api.github.com/repos/${{ github.repository }}/releases" -Headers @{ Authorization = "token ${{ secrets.GITHUB_TOKEN }}" } | |
| $DraftRelease = $Response | Where-Object -FilterScript { $_.draft -eq $true } | Select-Object -First 1 | |
| if (!$DraftRelease) { | |
| throw "No draft release found" | |
| } | |
| # Capture the draft release ID and tag | |
| $DRAFT_RELEASE_ID = $DraftRelease.id | |
| $DRAFT_RELEASE_TAG = $DraftRelease.tag_name | |
| # Save both the release ID and tag to environment variables for later steps | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "DRAFT_RELEASE_ID=$DRAFT_RELEASE_ID" | |
| Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "DRAFT_RELEASE_TAG=$DRAFT_RELEASE_TAG" | |
| Write-Host -Object "GitHub Draft ID: $DRAFT_RELEASE_ID" | |
| Write-Host -Object "GitHub Draft Tag: $DRAFT_RELEASE_TAG" | |
| - name: Uploading Assets to the Draft Release | |
| shell: pwsh | |
| run: | | |
| $Assets = @( | |
| @{ Path = "${{ env.MSIXBundle_PATH }}"; Name = "${{ env.MSIXBundle_NAME }}" } | |
| @{ Path = "${{ env.X64MSBuildLog_PATH }}"; Name = 'X64MSBuildLog.binlog' } | |
| @{ Path = "${{ env.ARM64MSBuildLog_PATH }}"; Name = 'ARM64MSBuildLog.binlog' } | |
| @{ Path = "${{ env.X64Symbol_PATH }}"; Name = "${{ env.X64Symbol_NAME }}" } | |
| @{ Path = "${{ env.ARM64Symbol_PATH }}"; Name = "${{ env.ARM64Symbol_NAME }}" } | |
| @{ Path = "${{ env.SBOM_PATH }}"; Name = "${{ env.SBOM_NAME }}" } | |
| ) | |
| [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID | |
| [string]$Repo = "${{ github.repository }}" | |
| [string]$AuthHeader = "token ${{ secrets.GITHUB_TOKEN }}" | |
| foreach ($Asset in $Assets) { | |
| if (-Not [string]::IsNullOrEmpty($Asset.Path)) { | |
| [string]$UploadUrl = "https://uploads.github.com/repos/$Repo/releases/$DraftReleaseId/assets?name=$($Asset.Name)" | |
| Write-Host -Object "Uploading $($Asset.Name) to draft release..." | |
| $null = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $Asset.Path -Headers @{ | |
| 'Authorization' = $AuthHeader | |
| 'Content-Type' = 'application/octet-stream' | |
| } | |
| } | |
| } | |
| - name: Uploading Artifact 1 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: ${{ env.MSIXBundle_NAME }} | |
| path: ${{ env.MSIXBundle_PATH }} | |
| - name: Uploading Artifact 2 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: X64MSBuildLog.binlog | |
| path: ${{ env.X64MSBuildLog_PATH }} | |
| - name: Uploading Artifact 3 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: ARM64MSBuildLog.binlog | |
| path: ${{ env.ARM64MSBuildLog_PATH }} | |
| - name: Uploading Artifact 4 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: ${{ env.X64Symbol_NAME }} | |
| path: ${{ env.X64Symbol_PATH }} | |
| - name: Uploading Artifact 5 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: ${{ env.ARM64Symbol_NAME }} | |
| path: ${{ env.ARM64Symbol_PATH }} | |
| - name: Uploading Artifact 6 | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| retention-days: 1 | |
| compression-level: 0 | |
| name: ${{ env.SBOM_NAME }} | |
| path: ${{ env.SBOM_PATH }} | |
| # The Attestation runs right after build and no other job, especially 3rd party actions/steps etc. should run in between. | |
| attest: | |
| needs: [build] | |
| runs-on: windows-2025 | |
| permissions: | |
| attestations: write | |
| id-token: write | |
| steps: | |
| - name: Downloading all Artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: Downloaded-Artifacts # Download all artifacts in this folder | |
| merge-multiple: true | |
| - name: Display structure of downloaded files | |
| run: ls -R Downloaded-Artifacts | |
| - name: Generating Artifact Attestation | |
| uses: actions/attest-build-provenance@v3 | |
| with: | |
| # subject-path: "${{ needs.build.outputs.MSIXBundle_PATH }}, ${{ needs.build.outputs.X64MSBuildLog_PATH }}, ${{ needs.build.outputs.ARM64MSBuildLog_PATH }}, ${{ needs.build.outputs.X64Symbol_PATH }}, ${{ needs.build.outputs.ARM64Symbol_PATH }}" | |
| subject-path: "Downloaded-Artifacts/*" # Use all the files in the artifact downloads folder | |
| SbomAttest: | |
| needs: [build] | |
| runs-on: windows-2025 | |
| permissions: | |
| contents: read | |
| attestations: write | |
| id-token: write | |
| steps: | |
| - name: Downloading all Artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: Downloaded-Artifacts # Download all artifacts in this folder | |
| merge-multiple: true | |
| - name: Display structure of downloaded files | |
| run: ls -R Downloaded-Artifacts | |
| - name: Generating SBOM attestation | |
| uses: actions/attest-sbom@v3 | |
| with: | |
| # subject-path: "${{ needs.build.outputs.MSIXBundle_PATH }}, ${{ needs.build.outputs.X64MSBuildLog_PATH }}, ${{ needs.build.outputs.ARM64MSBuildLog_PATH }}, ${{ needs.build.outputs.X64Symbol_PATH }}, ${{ needs.build.outputs.ARM64Symbol_PATH }}" | |
| subject-path: "Downloaded-Artifacts/*" # Use all the files in the artifact downloads folder | |
| sbom-path: ./Downloaded-Artifacts/manifest.spdx.json | |
| show-summary: true | |
| final: | |
| needs: [build, attest, SbomAttest] | |
| runs-on: windows-2025 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Check out the repository code | |
| uses: actions/checkout@v5 | |
| - name: Updating The MSIXBundle Download Links and Version via Pull Request | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| [string]$MSIXBundleName = "${{ needs.build.outputs.MSIXBundle_NAME }}" | |
| # Spaces in files added to the GitHub assets will be replaced with dots, so we need to do the same when constructing the download link | |
| [string]$AdjustedMSIXBundleName = $MSIXBundleName.Replace('AppControl Manager', 'AppControl.Manager') | |
| [string]$DRAFT_RELEASE_TAG = "${{ needs.build.outputs.DRAFT_RELEASE_TAG }}" | |
| [string]$GitHubRepository = "${{ github.repository }}" | |
| [string]$PACKAGE_VERSION = "${{ needs.build.outputs.PACKAGE_VERSION }}" | |
| # Construct the download URL using the draft release tag and MSIXBundle file name | |
| [string]$DownloadURLForMSIXBundle = "https://github.com/$GitHubRepository/releases/download/$DRAFT_RELEASE_TAG/$AdjustedMSIXBundleName" | |
| # Path to the files that will be updated | |
| [string]$DownloadURLFilePathForMSIXBundle = '.\AppControl Manager\MSIXBundleDownloadURL.txt' | |
| [string]$VersionFilePath = '.\AppControl Manager\version.txt' | |
| Set-Content -Path $DownloadURLFilePathForMSIXBundle -Value $DownloadURLForMSIXBundle -Force | |
| Write-Host -Object "Updated MSIXBundleDownloadURL.txt with download URL: $DownloadURLForMSIXBundle" | |
| Set-Content -Path $VersionFilePath -Value $PACKAGE_VERSION -Force | |
| Write-Host -Object "Updated version.txt with version: $PACKAGE_VERSION" | |
| # Configure Git for committing changes | |
| git config --global user.email '[email protected]' | |
| git config --global user.name 'HotCakeX' | |
| # Create a new branch for the pull request, making sure branch name has valid characters | |
| [string]$NewBranchName = 'AppControl-Manager-DownloadLink-Version-Update' | |
| git checkout -b $NewBranchName | |
| [string]$CommitMessageAndPRTitle = "AppControl-Manager-DownloadLink-Version-Update-Version-$PACKAGE_VERSION" | |
| # Stage and commit the change | |
| git add $DownloadURLFilePathForMSIXBundle | |
| git add $VersionFilePath | |
| git commit -m $CommitMessageAndPRTitle | |
| # Push the new branch to the remote repository | |
| git push -u origin $NewBranchName | |
| [string]$PRBody = @" | |
| This PR updates MSIXBundleDownloadURL.txt to | |
| `````` | |
| $DownloadURLForMSIXBundle | |
| `````` | |
| And version.txt to | |
| `````` | |
| $PACKAGE_VERSION | |
| `````` | |
| "@ | |
| # Create the pull request | |
| gh pr create --title $CommitMessageAndPRTitle --body $PRBody --base main --label 'Automated 🤖' --assignee HotCakeX | |
| - name: Add Body Text to the Draft Release | |
| shell: pwsh | |
| run: | | |
| $ReleaseId = "${{ needs.build.outputs.DRAFT_RELEASE_ID }}" | |
| $Repo = "${{ github.repository }}" | |
| [string]$Note = @" | |
| # Installation | |
| <a href="https://apps.microsoft.com/detail/9png1jddtgp8?mode=direct"> | |
| <img src="https://get.microsoft.com/images/en-us%20dark.svg" width="300" alt="install AppControl Manager from Microsoft Store"/> | |
| </a> | |
| **Install it from the Microsoft Store: https://apps.microsoft.com/detail/9png1jddtgp8** | |
| [**Documentation**](https://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager) | |
| ## What's New | |
| <!-- Main Content --> | |
| <br> | |
| How to [verify](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli) the MSIXBundle's authenticity: | |
| `````` | |
| gh attestation verify "Path To MSIXBundle" --repo HotCakeX/Harden-Windows-Security --format json | |
| `````` | |
| You can [install the GitHub CLI](https://github.com/cli/cli?tab=readme-ov-file#windows) from Winget: | |
| `````` | |
| winget install --id GitHub.cli | |
| `````` | |
| <br> | |
| "@ | |
| $Payload = @{ | |
| body = $Note | |
| tag_name = $DraftTag | |
| } | ConvertTo-Json | |
| $Url = "https://api.github.com/repos/$Repo/releases/$ReleaseId" | |
| Invoke-RestMethod -Uri $Url -Method Patch -Headers @{ | |
| "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" | |
| "Content-Type" = "application/json" | |
| } -Body $Payload |