Skip to content

Build AppControl Manager MSIX Package #126

Build AppControl Manager MSIX Package

Build AppControl Manager MSIX Package #126

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: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: windows-latest
# 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: powershell # Change to pwsh once Windows-latest starts using Server 2025 and change Winget file downloads to use -parallel
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 -Process {
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: Installing the necessary programs # Builds the AppControl Manager application securely with 0 usage of 3rd party tools, workflows and such.
shell: pwsh
run: |
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
$null = 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'
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed to install Visual Studio Build Tools: $LASTEXITCODE") }
Write-Host -Object "`nInstalling Visual C++ Redistributable" -ForegroundColor Magenta
$null = winget install --id Microsoft.VCRedist.2015+.x64 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed to install Microsoft.VCRedist: $LASTEXITCODE") }
- name: Check out the repository code
uses: actions/checkout@v4
- 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
)
[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'
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 ---
### ManageDefender
. $MSBuildPath 'eXclude\C++ WMI Interop\ManageDefender\ManageDefender.slnx' /p:Configuration=Release /p:Platform=x64 /target:"clean;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building MS Defender solution for X64. Exit Code: $LASTEXITCODE") }
. $MSBuildPath 'eXclude\C++ WMI Interop\ManageDefender\ManageDefender.slnx' /p:Configuration=Release /p:Platform=arm64 /target:"clean;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building MS Defender solution for ARM64. Exit Code: $LASTEXITCODE") }
### ScheduledTaskManager
. $MSBuildPath 'eXclude\C++ ScheduledTaskManager\ScheduledTaskManager\ScheduledTaskManager.slnx' /p:Configuration=Release /p:Platform=x64 /target:"clean;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ScheduledTaskManager solution for X64. Exit Code: $LASTEXITCODE") }
. $MSBuildPath 'eXclude\C++ ScheduledTaskManager\ScheduledTaskManager\ScheduledTaskManager.slnx' /p:Configuration=Release /p:Platform=arm64 /target:"clean;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ScheduledTaskManager 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;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building the Shell solution for X64. Exit Code: $LASTEXITCODE") }
. $MSBuildPath 'eXclude\Shell\Shell.slnx' /p:Configuration=Release /p:Platform=arm64 /target:"clean;Build"
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building the Shell solution for ARM64. Exit Code: $LASTEXITCODE") }
#endregion
#region --- RUST projects ---
rustup default stable
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed setting Rust toolchain to Stable. Exit Code: $LASTEXITCODE") }
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 WMI Interop\Device Guard\Program'
if (Test-Path -PathType Leaf -LiteralPath 'Cargo.lock') {
Remove-Item -Force -LiteralPath 'Cargo.lock'
}
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 Device Guard Rust project. Exit Code: $LASTEXITCODE") }
cargo build_arm64
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building arm64 Device Guard Rust project. Exit Code: $LASTEXITCODE") }
Set-Location -Path $Current_Location
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") }
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\C++ ScheduledTaskManager\ScheduledTaskManager\x64\Release\ScheduledTaskManager-x64.exe' -Destination '.\CppInterop\ScheduledTaskManager.exe' -Force
Copy-Item -Path '.\eXclude\C++ WMI Interop\ManageDefender\x64\Release\ManageDefender-x64.exe' -Destination '.\CppInterop\ManageDefender.exe' -Force
Copy-Item -Path '.\eXclude\Rust WMI Interop\Device Guard\Program\target\x86_64-pc-windows-msvc\release\DeviceGuardWMIRetriever-X64.exe' -Destination '.\RustInterop\DeviceGuardWMIRetriever.exe' -Force
Copy-Item -Path '.\eXclude\Rust Interop Library\target\x86_64-pc-windows-msvc\release\rust_interop.lib' -Destination '.\RustInterop\rust_interop.lib' -Force
# Generate for X64 architecture
dotnet build 'AppControl Manager.slnx' --configuration Release --verbosity minimal /p:Platform=x64
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building x64 AppControl Manager project. Exit Code: $LASTEXITCODE") }
dotnet msbuild 'AppControl Manager.slnx' /p:Configuration=Release /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") }
# 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\C++ ScheduledTaskManager\ScheduledTaskManager\ARM64\Release\ScheduledTaskManager-ARM64.exe' -Destination '.\CppInterop\ScheduledTaskManager.exe' -Force
Copy-Item -Path '.\eXclude\C++ WMI Interop\ManageDefender\ARM64\Release\ManageDefender-ARM64.exe' -Destination '.\CppInterop\ManageDefender.exe' -Force
Copy-Item -Path '.\eXclude\Rust WMI Interop\Device Guard\Program\target\aarch64-pc-windows-msvc\release\DeviceGuardWMIRetriever-ARM64.exe' -Destination '.\RustInterop\DeviceGuardWMIRetriever.exe' -Force
Copy-Item -Path '.\eXclude\Rust Interop Library\target\aarch64-pc-windows-msvc\release\rust_interop.lib' -Destination '.\RustInterop\rust_interop.lib' -Force
# Generate for ARM64 architecture
dotnet build 'AppControl Manager.slnx' --configuration Release --verbosity minimal /p:Platform=ARM64
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New("Failed building ARM64 AppControl Manager project. Exit Code: $LASTEXITCODE") }
dotnet msbuild 'AppControl Manager.slnx' /p:Configuration=Release /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+_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+_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_x64\.msixsym' -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
#region Finding ARM64 outputs
[System.String]$FinalMSIXARM64Path = Get-MSIXFile -BasePath ([System.IO.Path]::Combine($PWD.Path, 'MSIXOutputARM64')) -FolderPattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_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+_Test' -FileNamePattern 'AppControl Manager_\d+\.\d+\.\d+\.\d+_arm64\.msixsym' -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')
[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)
[System.Text.RegularExpressions.Match]$MatchARM64 = $versionRegexARM64.Match($FinalMSIXARM64Name)
if (!$MatchX64.Success) {
throw [System.InvalidOperationException]::New('Could not detect version from X64 file name')
}
if (!$MatchARM64.Success) {
throw [System.InvalidOperationException]::New('Could not detect version from ARM64 file name')
}
[System.String]$versionX64 = $MatchX64.Groups[1].Value
[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)
[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
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 build '.\eXclude\PartnerCenter\PartnerCenter.slnx' --configuration Release --verbosity minimal
dotnet msbuild '.\eXclude\PartnerCenter\PartnerCenter.slnx' /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 $false -Workflow $true -UpdateWorkLoads $false -Install $false -Upload $false
# For GitHub workflow 2
# Build_ACM -Type Store -DownloadRepo $false -InstallDeps $false -Workflow $true -UpdateWorkLoads $false -Install $false -Upload $true
# 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
# 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
- 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@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.MSIXBundle_NAME }}
path: ${{ env.MSIXBundle_PATH }}
- name: Uploading Artifact 2
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: X64MSBuildLog.binlog
path: ${{ env.X64MSBuildLog_PATH }}
- name: Uploading Artifact 3
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ARM64MSBuildLog.binlog
path: ${{ env.ARM64MSBuildLog_PATH }}
- name: Uploading Artifact 4
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.X64Symbol_NAME }}
path: ${{ env.X64Symbol_PATH }}
- name: Uploading Artifact 5
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.ARM64Symbol_NAME }}
path: ${{ env.ARM64Symbol_PATH }}
- name: Uploading Artifact 6
uses: actions/upload-artifact@v4
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@v4
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@v2
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@v4
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@v2
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@v4
- 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