Skip to content

Build AppControl Manager MSIX Package #54

Build AppControl Manager MSIX Package

Build AppControl Manager MSIX Package #54

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-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 }}
MSIX_PATH: ${{ steps.main_buildOp.outputs.MSIX_PATH }}
MSIX_NAME: ${{ steps.main_buildOp.outputs.MSIX_NAME }}
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 }}
permissions:
contents: write # Required for adding files to the GitHub draft release
steps:
# Builds the AppControl Manager application securely with 0 usage of 3rd party tools, workflows and such.
- name: Installing the necessary programs
run: |
winget source update
Write-Host -Object "`nInstalling .NET SDK" -ForegroundColor Magenta
$null = winget install --id Microsoft.DotNet.SDK.9 --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') }
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.ManagedDesktop --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Workload.UniversalBuildTools --add Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --add Microsoft.VisualStudio.Component.UWP.VC.ARM64'
if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New('Failed to install Visual Studio Build Tools') }
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
- name: Check out the repository code
uses: actions/checkout@v4
- name: Building And Packaging the AppControl Manager
id: main_buildOp
shell: pwsh
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: |
$global:ErrorActionPreference = 'Stop'
[System.String]$AppControlManagerDirectory = $PWD.Path
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
# 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
}
}
# https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build
# https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference
# https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties
# Generate for X64 architecture
dotnet build 'AppControl Manager.sln' --configuration Release --verbosity minimal /p:Platform=x64
dotnet msbuild 'AppControl Manager.sln' /p:Configuration=Release /p:AppxPackageDir="MSIXOutputX64\" /p:GenerateAppxPackageOnBuild=true /p:Platform=x64 -v:minimal /p:MsPdbCmfExeFullpath=$mspdbcmfPath -bl:X64MSBuildLog.binlog
# Generate for ARM64 architecture
dotnet build 'AppControl Manager.sln' --configuration Release --verbosity minimal /p:Platform=ARM64
dotnet msbuild 'AppControl Manager.sln' /p:Configuration=Release /p:AppxPackageDir="MSIXOutputARM64\" /p:GenerateAppxPackageOnBuild=true /p:Platform=ARM64 -v:minimal /p:MsPdbCmfExeFullpath=$mspdbcmfPath -bl:ARM64MSBuildLog.binlog
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($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/en-us/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
[XML]$CSProjXMLContent = Get-Content -Path '.\AppControl Manager.csproj' -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 MSIXPath, MSIXName and 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 "MSIX_PATH=$FinalMSIXX64Path"
Add-Content -Path ($env:GITHUB_ENV, $env:GITHUB_OUTPUT) -Value "MSIX_NAME=$FinalMSIXX64Name"
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"
- 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.MSIX_PATH }}"; Name = "${{ env.MSIX_NAME }}" }
@{ 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 }}" }
)
[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.MSIX_NAME }}
path: ${{ env.MSIX_PATH }}
- name: Uploading Artifact 2
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.MSIXBundle_NAME }}
path: ${{ env.MSIXBundle_PATH }}
- name: Uploading Artifact 3
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: X64MSBuildLog.binlog
path: ${{ env.X64MSBuildLog_PATH }}
- name: Uploading Artifact 4
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ARM64MSBuildLog.binlog
path: ${{ env.ARM64MSBuildLog_PATH }}
- name: Uploading Artifact 5
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.X64Symbol_NAME }}
path: ${{ env.X64Symbol_PATH }}
- name: Uploading Artifact 6
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: ${{ env.ARM64Symbol_NAME }}
path: ${{ env.ARM64Symbol_PATH }}
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.MSIX_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:
needs: build
runs-on: windows-2025
permissions:
contents: read
steps:
- name: Check out the repository code
uses: actions/checkout@v4
- name: Generating SBOM
uses: anchore/sbom-action@v0
with:
dependency-snapshot: false # This would require more permissions than "contents: read". This action is not to be trusted with more permissions.
upload-release-assets: false # Done manually in next steps
upload-artifact: false # Done manually in next steps
output-file: ./HardenWindowsSecurityRepoSBOM.spdx
# artifact-name: HardenWindowsSecurityRepoSBOM.spdx
- name: Uploading Artifact 7
uses: actions/upload-artifact@v4
with:
retention-days: 1
compression-level: 0
name: HardenWindowsSecurityRepoSBOM.spdx
path: ./HardenWindowsSecurityRepoSBOM.spdx
SbomAttest:
needs: [build, sbom]
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.MSIX_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/HardenWindowsSecurityRepoSBOM.spdx
show-summary: true
final:
needs: [build, attest, sbom, SbomAttest]
runs-on: windows-2025
permissions:
contents: write
pull-requests: write
steps:
- name: Check out the repository code
uses: actions/checkout@v4
- name: Downloading an Artifact
uses: actions/download-artifact@v4
with:
name: HardenWindowsSecurityRepoSBOM.spdx
path: Downloaded-Artifacts
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R Downloaded-Artifacts
- name: Uploading the SBOM file to the Draft Release
shell: pwsh
run: |
[string]$DraftReleaseId = ${{ needs.build.outputs.DRAFT_RELEASE_ID }}
[string]$FilePath = ".\Downloaded-Artifacts\HardenWindowsSecurityRepoSBOM.spdx"
[string]$FileName = "HardenWindowsSecurityRepoSBOM.spdx"
[string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName"
$Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{
"Authorization" = "token ${{ secrets.GITHUB_TOKEN }}"
"Content-Type" = "application/octet-stream"
}
Write-Host -Object "Uploaded the SBOM file to the draft release: $Response.name"
- name: Updating The MSIX/MSIXBundle Download Links and Version via Pull Request
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
[string]$MSIXName = "${{ needs.build.outputs.MSIX_NAME }}"
[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]$AdjustedMSIXName = $MSIXName.Replace('AppControl Manager', 'AppControl.Manager')
[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 MSIX file name
[string]$DownloadURL = "https://github.com/$GitHubRepository/releases/download/$DRAFT_RELEASE_TAG/$AdjustedMSIXName"
[string]$DownloadURLForMSIXBundle = "https://github.com/$GitHubRepository/releases/download/$DRAFT_RELEASE_TAG/$AdjustedMSIXBundleName"
# Path to the files that will be updated
[string]$DownloadURLFilePath = '.\AppControl Manager\DownloadURL.txt'
[string]$DownloadURLFilePathForMSIXBundle = '.\AppControl Manager\MSIXBundleDownloadURL.txt'
[string]$VersionFilePath = '.\AppControl Manager\version.txt'
# Update the file content with the new URL
Set-Content -Path $DownloadURLFilePath -Value $DownloadURL -Force
Write-Host -Object "Updated DownloadURL.txt with download URL: $DownloadURL"
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 'spynetgirl@outlook.com'
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 $DownloadURLFilePath
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 DownloadURL.txt to
``````
$DownloadURL
``````
And 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 = @"
# What's New
<br>
> [!IMPORTANT]\
> **How To Install: Copy and Paste this command in a PowerShell window as Admin. ([Technical explanation available here](https://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager#how-to-install-or-update-the-app))**
> ``````powershell
> (irm 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Harden-Windows-Security.ps1')+'AppControl'|iex
> ``````
<br>
<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>
> [!NOTE]\
> As mentioned at the top, please **[refer to this page](https://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager#how-to-install-or-update-the-app)** for installation instructions.
<br>
"@
$Payload = @{ body = $Note } | 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