From 99aff604d3d14563de1d1e292182b3b060e24bf6 Mon Sep 17 00:00:00 2001 From: Violet Hansen Date: Fri, 7 Feb 2025 11:44:35 +0200 Subject: [PATCH] Improved build process and added ARM64 support (#585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ The GitHub releases for the AppControl Manager will include symbol files for debugging purposes for both X64 and ARM64 architectures. They will also include complete build logs for end-user review. All of these additional files are securely attested and verified by the workflow. ✅ The entire process is completely transparent. ✅ Provided instructions for building the AppControl Manager locally on your own device directly from the source code using no 3rd party tools at all. ✅ The build process has also been improved in the GitHub workflow by uploading the generated files to the release first before running any other actions. This can improve security and ensure no action can run prior to package building and upload. ✅The Bootstrapper has been improved, reducing number of lines of codes in it and also added the ability to install MSIXBundle files which include ARM64 and X64 support for AppControl Manager. ✅ AppControl Manager version bump to 1.8.9.0 ✅ Updated Nuget dependency: Microsoft.Graphics.Win2D --- .../Build AppControl Manager MSIX Package.yml | 409 +++++++++++++++--- .gitignore | 4 +- AppControl Manager/AppControl Manager.csproj | 39 +- AppControl Manager/MSIXBundleDownloadURL.txt | 1 + AppControl Manager/Package.appxmanifest | 2 +- AppControl Manager/app.manifest | 2 +- Harden-Windows-Security.ps1 | 88 ++-- .../AppControl Manager/AppControl Manager.md | 278 +++++++++++- 8 files changed, 690 insertions(+), 133 deletions(-) create mode 100644 AppControl Manager/MSIXBundleDownloadURL.txt diff --git a/.github/workflows/Build AppControl Manager MSIX Package.yml b/.github/workflows/Build AppControl Manager MSIX Package.yml index 5690706c6..656a65b84 100644 --- a/.github/workflows/Build AppControl Manager MSIX Package.yml +++ b/.github/workflows/Build AppControl Manager MSIX Package.yml @@ -21,8 +21,7 @@ jobs: shell: pwsh run: | # Retrieve the latest Winget release information - [string]$WingetRepoURL = 'https://api.github.com/repos/microsoft/winget-cli/releases' - $WingetReleases = Invoke-RestMethod -Uri $WingetRepoURL + $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 @@ -55,66 +54,260 @@ jobs: - name: Installing the necessary programs run: | winget source update - winget install --id Microsoft.DotNet.SDK.9 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget - winget install --id Microsoft.VisualStudio.2022.BuildTools --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget - winget install --id Microsoft.WindowsSDK.10.0.26100 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget - # https://github.com/microsoft/winget-cli/issues/1705 - winget install --id Microsoft.AppInstaller --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget - winget install --id Microsoft.VCRedist.2015+.x64 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget - - - name: Building the AppControl Manager + + 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.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' + 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: Building And Packaging the AppControl Manager 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 - - # 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 run: | - Write-Host -Object "`nChecking .NET info`n`n" + $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" + Write-Host -Object "`nListing installed .NET SDKs`n`n" -ForegroundColor Magenta dotnet --list-sdks - dotnet build "AppControl Manager.sln" --configuration Release --verbosity normal /p:Platform=x64 - - - name: Generating the MSIX Package - working-directory: './AppControl Manager' - run: dotnet msbuild "AppControl Manager.sln" /p:Configuration=Release /p:AppxPackageDir="MSIXOutput\" /p:GenerateAppxPackageOnBuild=true /p:Platform=x64 -v:normal - - name: Capturing the Generated MSIX file Path - shell: pwsh - run: | - [string]$MSIXPath = (Get-ChildItem -File -Path '.\AppControl Manager\MSIXOutput\AppControl Manage*\AppControl Manager*.msix').FullName - [string]$MSIXName = (Get-ChildItem -File -Path '.\AppControl Manager\MSIXOutput\AppControl Manage*\AppControl Manager*.msix').Name - [XML]$CSProjXMLContent = Get-Content -Path '.\AppControl Manager\AppControl Manager.csproj' -Force + 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 + Write-Host -Object "Setting environment variable: $name=$value" + } + } + + # 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($MSIXPath) -or [string]::IsNullOrWhiteSpace($MSIXName) -or [string]::IsNullOrWhiteSpace($MSIXVersion)) { throw "Couldn't find the generated MSIX package" } + 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 -Value "MSIX_PATH=$MSIXPath" - Add-Content -Path $env:GITHUB_ENV -Value "MSIX_NAME=$MSIXName" - Add-Content -Path $env:GITHUB_ENV -Value "MSIX_VERSION=$MSIXVersion" + Add-Content -Path $env:GITHUB_ENV -Value "MSIX_PATH=$FinalMSIXX64Path" + Add-Content -Path $env:GITHUB_ENV -Value "MSIX_NAME=$FinalMSIXX64Name" + Add-Content -Path $env:GITHUB_ENV -Value "PACKAGE_VERSION=$MSIXVersion" - - name: Generating Artifact Attestation - uses: actions/attest-build-provenance@v2 - with: - subject-path: ${{ env.MSIX_PATH }} + # Saving the details for the MSIX Bundle file + Add-Content -Path $env:GITHUB_ENV -Value "MSIXBundle_PATH=$MSIXBundle" + Add-Content -Path $env:GITHUB_ENV -Value "MSIXBundle_NAME=$FinalBundleFileName" - - name: Generating SBOM - uses: anchore/sbom-action@v0 - with: - dependency-snapshot: true - upload-release-assets: false - upload-artifact: true - output-file: ./HardenWindowsSecurityRepoSBOM.spdx - artifact-name: HardenWindowsSecurityRepoSBOM.spdx + # Saving the details of the log files + Add-Content -Path $env:GITHUB_ENV -Value "X64MSBuildLog_PATH=X64MSBuildLog.binlog" + Add-Content -Path $env:GITHUB_ENV -Value "ARM64MSBuildLog_PATH=ARM64MSBuildLog.binlog" - - name: Generating SBOM attestation - uses: actions/attest-sbom@v2 - with: - subject-path: ${{ env.MSIX_PATH }} - sbom-path: ./HardenWindowsSecurityRepoSBOM.spdx - show-summary: true + # Saving the details of the X64 symbol file + Add-Content -Path $env:GITHUB_ENV -Value "X64Symbol_PATH=$FinalMSIXX64SymbolPath" + Add-Content -Path $env:GITHUB_ENV -Value "X64Symbol_NAME=$FinalMSIXX64SymbolName" + + # Saving the details of the ARM64 symbol file + Add-Content -Path $env:GITHUB_ENV -Value "ARM64Symbol_PATH=$FinalMSIXARM64SymbolPath" + Add-Content -Path $env:GITHUB_ENV -Value "ARM64Symbol_NAME=$FinalMSIXARM64SymbolName" - name: Finding the Latest Draft Release id: find_draft_release @@ -153,6 +346,99 @@ jobs: } Write-Host -Object "Uploaded package to draft release: $Response.name" + - name: Uploading the MSIXBundle to the Draft Release + shell: pwsh + run: | + [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID + [string]$FilePath = "${{ env.MSIXBundle_PATH }}" + [string]$FileName = "${{ env.MSIXBundle_NAME }}" + [string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName" + # Upload the package to the draft release + $Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{ + "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" + "Content-Type" = "application/octet-stream" + } + Write-Host -Object "Uploaded package to draft release: $Response.name" + + - name: Uploading the Logs to the Draft Release + working-directory: './AppControl Manager' + shell: pwsh + run: | + [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID + [string]$FilePath = "${{ env.X64MSBuildLog_PATH }}" + [string]$FileName = "X64MSBuildLog.binlog" + [string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName" + # Upload the log to the draft release + $Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{ + "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" + "Content-Type" = "application/octet-stream" + } + Write-Host -Object "Uploaded log to draft release: $Response.name" + + - name: Uploading the Logs to the Draft Release + working-directory: './AppControl Manager' + shell: pwsh + run: | + [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID + [string]$FilePath = "${{ env.ARM64MSBuildLog_PATH }}" + [string]$FileName = "ARM64MSBuildLog.binlog" + [string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName" + # Upload the log to the draft release + $Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{ + "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" + "Content-Type" = "application/octet-stream" + } + Write-Host -Object "Uploaded log to draft release: $Response.name" + + - name: Uploading the X64 Symbol to the Draft Release + shell: pwsh + run: | + [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID + [string]$FilePath = "${{ env.X64Symbol_PATH }}" + [string]$FileName = "${{ env.X64Symbol_NAME }}" + [string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName" + # Upload the X64 Symbol to the draft release + $Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{ + "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" + "Content-Type" = "application/octet-stream" + } + Write-Host -Object "Uploaded X64 Symbol to draft release: $Response.name" + + - name: Uploading the ARM64 Symbol to the Draft Release + shell: pwsh + run: | + [string]$DraftReleaseId = $env:DRAFT_RELEASE_ID + [string]$FilePath = "${{ env.ARM64Symbol_PATH }}" + [string]$FileName = "${{ env.ARM64Symbol_NAME }}" + [string]$UploadUrl = "https://uploads.github.com/repos/${{ github.repository }}/releases/$DraftReleaseId/assets?name=$FileName" + # Upload the ARM64 Symbol to the draft release + $Response = Invoke-RestMethod -Uri $UploadUrl -Method Put -InFile $FilePath -Headers @{ + "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}" + "Content-Type" = "application/octet-stream" + } + Write-Host -Object "Uploaded ARM64 Symbol to draft release: $Response.name" + + - name: Generating Artifact Attestation + uses: actions/attest-build-provenance@v2 + with: + subject-path: "${{ env.MSIX_PATH }}, ${{ env.MSIXBundle_PATH }}, ${{ env.X64MSBuildLog_PATH }}, ${{ env.ARM64MSBuildLog_PATH }}, ${{ env.X64Symbol_PATH }}, ${{ env.ARM64MSBuildLog_PATH }}" + + - name: Generating SBOM + uses: anchore/sbom-action@v0 + with: + dependency-snapshot: true + upload-release-assets: true + upload-artifact: true + output-file: ./HardenWindowsSecurityRepoSBOM.spdx + artifact-name: HardenWindowsSecurityRepoSBOM.spdx + + - name: Generating SBOM attestation + uses: actions/attest-sbom@v2 + with: + subject-path: "${{ env.MSIX_PATH }}, ${{ env.MSIXBundle_PATH }}, ${{ env.X64MSBuildLog_PATH }}, ${{ env.ARM64MSBuildLog_PATH }}, ${{ env.X64Symbol_PATH }}, ${{ env.ARM64MSBuildLog_PATH }}" + sbom-path: ./HardenWindowsSecurityRepoSBOM.spdx + show-summary: true + - name: Uploading the SBOM file to the Draft Release shell: pwsh run: | @@ -167,33 +453,40 @@ jobs: } Write-Host -Object "Uploaded the SBOM file to the draft release: $Response.name" - - name: Updating The MSIX Download Link and Version via Pull Request + - name: Updating The MSIX/MSIXBundle Download Links and Version via Pull Request shell: pwsh env: GH_TOKEN: ${{ github.token }} run: | [string]$MSIXName = "${{ env.MSIX_NAME }}" + [string]$MSIXBundleName = "${{ env.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 = "${{ env.DRAFT_RELEASE_TAG }}" [string]$GitHubRepository = "${{ github.repository }}" - [string]$MSIX_VERSION = "${{ env.MSIX_VERSION }}" + [string]$PACKAGE_VERSION = "${{ env.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 $VersionFilePath -Value $MSIX_VERSION -Force - Write-Host -Object "Updated version.txt with version: $MSIX_VERSION" + 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' @@ -203,10 +496,11 @@ jobs: [string]$NewBranchName = 'AppControl-Manager-DownloadLink-Version-Update' git checkout -b $NewBranchName - [string]$CommitMessageAndPRTitle = "AppControl-Manager-DownloadLink-Version-Update-Version-$MSIX_VERSION" + [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 @@ -218,11 +512,14 @@ jobs: `````` $DownloadURL `````` + And MSIXBundleDownloadURL.txt to + `````` + $DownloadURLForMSIXBundle + `````` And version.txt to `````` - $MSIX_VERSION + $PACKAGE_VERSION `````` - For the file: $AdjustedMSIXName "@ diff --git a/.gitignore b/.gitignore index f53fcb830..cc2b266ef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ Harden-Windows-Security Module/Harden Windows Security.csproj.user AppControl Manager/.vs/ AppControl Manager/bin/ AppControl Manager/obj/ -AppControl Manager/MSIXOutput +AppControl Manager/MSIXOutputX64 +AppControl Manager/MSIXOutputARM64 +AppControl Manager/MSIXBundleOutput AppControl Manager/Generated Files/ AppControl Manager/AppControl Manager.csproj.user diff --git a/AppControl Manager/AppControl Manager.csproj b/AppControl Manager/AppControl Manager.csproj index 6063fbaf6..f68fc3017 100644 --- a/AppControl Manager/AppControl Manager.csproj +++ b/AppControl Manager/AppControl Manager.csproj @@ -81,7 +81,7 @@ - MSIXOutput\ + MSIXOutputX64\ True True Always @@ -89,9 +89,10 @@ 0 True AppControlManager + False send - 1.8.8.0 + 1.8.9.0 $(FileVersion) en-US LICENSE @@ -112,7 +113,8 @@ - + + @@ -143,7 +145,7 @@ - + all @@ -151,7 +153,8 @@ - + + @@ -254,9 +257,29 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppControl Manager/MSIXBundleDownloadURL.txt b/AppControl Manager/MSIXBundleDownloadURL.txt new file mode 100644 index 000000000..98814daf1 --- /dev/null +++ b/AppControl Manager/MSIXBundleDownloadURL.txt @@ -0,0 +1 @@ +https://github.com/HotCakeX/Harden-Windows-Security/releases/download/AppControlManager.v.1.8.8.0/AppControl.Manager_1.8.8.0_x64.msix diff --git a/AppControl Manager/Package.appxmanifest b/AppControl Manager/Package.appxmanifest index 1f9c2f4cc..da6bc5095 100644 --- a/AppControl Manager/Package.appxmanifest +++ b/AppControl Manager/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="1.8.9.0" /> diff --git a/AppControl Manager/app.manifest b/AppControl Manager/app.manifest index 5146bf162..485fa5e3c 100644 --- a/AppControl Manager/app.manifest +++ b/AppControl Manager/app.manifest @@ -2,7 +2,7 @@ - + diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index b5aeb457a..e8144d137 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -37,33 +37,29 @@ Function AppControl { .DESCRIPTION Please refer to the provided link for all of the information about this function and detailed overview of the entire process. https://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager + .PARAMETER MSIXBundlePath + The path to the AppControlManager MSIXBundle file. If not provided, the latest MSIXBundle file will be downloaded from the GitHub. .PARAMETER MSIXPath The path to the AppControlManager MSIX file. If not provided, the latest MSIX file will be downloaded from the GitHub. It must have the version number and architecture in its file name as provided on GitHub or produced by Visual Studio. .PARAMETER SignTool The path to the Microsoft's Signtool.exe; If not provided, the function automatically downloads the latest SignTool.exe from the Microsoft website in Nuget and will use it for the signing operations. #> [CmdletBinding()] - param ([Parameter(Mandatory = $false)][string]$MSIXPath, [Parameter(Mandatory = $False)][string]$SignTool) + param ([Parameter(Mandatory = $false)][string]$MSIXBundlePath, [Parameter(Mandatory = $false)][string]$MSIXPath, [Parameter(Mandatory = $False)][string]$SignTool) + $ErrorActionPreference = 'Stop' if ($ExecutionContext.SessionState.LanguageMode -ne 'ConstrainedLanguage') { # We cannot use .NET methods in ConstrainedLanguage mode if (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Warning -Message 'Please run this function as an Administrator'; return } } - $ErrorActionPreference = 'Stop' - Write-Verbose -Message 'Detecting the CPU Arch' - switch ($Env:PROCESSOR_ARCHITECTURE) { - 'AMD64' { [string]$CPUArch = 'x64'; break } - 'ARM64' { [string]$CPUArch = 'arm64'; break } - default { Throw [System.PlatformNotSupportedException] 'Only AMD64 and ARM64 architectures are supported.' } - } [string]$CommonName = 'SelfSignedCertForAppControlManager' [string]$WorkingDir = Join-Path -Path $env:TEMP -ChildPath $CommonName [string]$CertificateOutputPath = Join-Path -Path $WorkingDir -ChildPath "$CommonName.cer" [string]$HashingAlgorithm = 'Sha512' - # Pattern for AppControl Manager version and architecture extraction from file path and download link URL - [regex]$RegexPattern = '_(?\d+\.\d+\.\d+\.\d+)_(?x64|arm64)\.msix$' - + [string]$_Package # Where the final package path will be stored, whether it's MSIX or MSIXBundle + [string]$CPUArch = @{AMD64 = 'x64'; ARM64 = 'arm64' }[$Env:PROCESSOR_ARCHITECTURE] + if ([System.String]::IsNullOrWhiteSpace($CPUArch)) { throw [System.PlatformNotSupportedException] 'Only AMD64 and ARM64 architectures are supported.' } Write-Verbose -Message 'Creating the working directory in the TEMP directory' if (Test-Path -Path $WorkingDir -PathType Container) { Remove-Item -Path $WorkingDir -Recurse -Force } $null = New-Item -Path $WorkingDir -ItemType Directory -Force @@ -104,53 +100,40 @@ Function AppControl { Write-Verbose -Message 'Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet and Downloading it' [string]$LatestSignToolVersion = (Invoke-RestMethod -Uri 'https://api.nuget.org/v3-flatcontainer/microsoft.windows.sdk.buildtools/index.json').versions | Select-Object -Last 1 Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/microsoft.windows.sdk.buildtools/${LatestSignToolVersion}/microsoft.windows.sdk.buildtools.${LatestSignToolVersion}.nupkg" -OutFile (Join-Path -Path $WorkingDir -ChildPath 'Microsoft.Windows.SDK.BuildTools.zip') - Write-Verbose -Message 'Extracting the nupkg' - Expand-Archive -Path "$WorkingDir\Microsoft.Windows.SDK.BuildTools.zip" -DestinationPath $WorkingDir -Force # Saving .nupkg as .zip to satisfy Windows PowerShell - Write-Verbose -Message 'Finding the Signtool.exe path in the extracted directory' + Write-Verbose -Message 'Extracting the nupkg and finding the Signtool.exe path in the extracted directory' + Expand-Archive -Path (Join-Path -Path $WorkingDir -ChildPath 'Microsoft.Windows.SDK.BuildTools.zip') -DestinationPath $WorkingDir -Force # Saving .nupkg as .zip to satisfy Windows PowerShell [string]$SignTool = (Get-Item -Path "$WorkingDir\bin\*\$CPUArch\signtool.exe").FullName } - # Download the MSIX package if user did not provide the path to it - if ([string]::IsNullOrWhiteSpace($MSIXPath)) { - [string]$MSIXPath = Join-Path -Path $WorkingDir -ChildPath 'AppControl.Manager.msix' + # If user provided a valid path to the MSIXBundle file + if (![string]::IsNullOrWhiteSpace($MSIXBundlePath) -and (Test-Path -Path $MSIXBundlePath -PathType Leaf)) { + $_Package = $MSIXBundlePath + } + # If user provided a valid path to the MSIX file + elseif (![string]::IsNullOrWhiteSpace($MSIXPath) -and (Test-Path -Path $MSIXPath -PathType Leaf)) { + $_Package = $MSIXPath + } + # Download the MSIXBundle if user didn't provide any paths + else { + Write-Verbose -Message 'Downloading the latest AppControl Manager MSIXBundle file from GitHub' + $_Package = Join-Path -Path $WorkingDir -ChildPath 'AppControlManager.msixbundle' # Download link for the latest version of AppControl manger is retrieved from this text file - [string]$MSIXPackageDownloadURL = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/refs/heads/main/AppControl%20Manager/DownloadURL.txt' - - Write-Verbose -Message 'Downloading the MSIX package from the GitHub releases' -Verbose - $null = Invoke-WebRequest -Uri $MSIXPackageDownloadURL -OutFile $MSIXPath + [string]$MSIXBundleDownloadURL = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/refs/heads/main/AppControl%20Manager/MSIXBundleDownloadURL.txt' - # Get the version and architecture of the installing MSIX package app from the download URL - [System.Text.RegularExpressions.Match]$RegexMatch = $RegexPattern.Match($MSIXPackageDownloadURL) - if ($RegexMatch.Success) { - [string]$InstallingAppVersion = $RegexMatch.Groups['Version'].Value - [string]$InstallingAppArchitecture = $RegexMatch.Groups['Architecture'].Value - } - else { - throw 'Could not get the version of the installing app from the MSIX download URL.' - } + Write-Verbose -Message 'Downloading the MSIXBundle from the GitHub releases' -Verbose + $null = Invoke-WebRequest -Uri $MSIXBundleDownloadURL -OutFile $MSIXPath } - else { - # Get the version and architecture of the installing MSIX package app from the User provided file path - [System.Text.RegularExpressions.Match]$RegexMatch = $RegexPattern.Match($MSIXPath) - if ($RegexMatch.Success) { - [string]$InstallingAppVersion = $RegexMatch.Groups['Version'].Value - [string]$InstallingAppArchitecture = $RegexMatch.Groups['Architecture'].Value - } - else { - throw 'Could not get the version of the installing app from the -MSIX parameter value that you provided.' - } - } - Write-Verbose -Message 'Signing the App Control Manager MSIX package' + Write-Verbose -Message 'Signing the App Control Manager package' # In this step the SignTool detects the cert to use based on Common name + ThumbPrint + Hash Algo + Store Type + Store Name if ($VerbosePreference -eq 'Continue') { # Displays full debug logs if -Verbose is used or Verbose preference of the session is set to Continue - . $SignTool sign /debug /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $MSIXPath + . $SignTool sign /debug /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $_Package } else { # Displays no output if the command runs successfully, and displays minimal output if the command fails. - $null = . $SignTool sign /q /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $MSIXPath + $null = . $SignTool sign /q /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $_Package } if ($LASTEXITCODE -ne 0) { throw "SignTool Failed. Exit Code: $LASTEXITCODE" } @@ -164,11 +147,6 @@ Function AppControl { [string[]]$PossiblePreviousASRExclusions = (Get-MpPreference).AttackSurfaceReductionOnlyExclusions | Where-Object -FilterScript { $_ -like '*__sadt7br7jpt02\AppControlManager*' } if ($null -ne $PossiblePreviousASRExclusions -and $PossiblePreviousASRExclusions.Length -gt 0) { Remove-MpPreference -AttackSurfaceReductionOnlyExclusions $PossiblePreviousASRExclusions } - [string]$InstallingAppLocationToAdd = 'C:\Program Files\WindowsApps\AppControlManager_' + $InstallingAppVersion + '_' + $InstallingAppArchitecture + '__sadt7br7jpt02\' - Write-Verbose -Message "Adding the new app install's files To the ASR Rules exclusions." - # The cmdlet won't add duplicates - Add-MpPreference -AttackSurfaceReductionOnlyExclusions (($InstallingAppLocationToAdd + 'AppControlManager.exe'), ($InstallingAppLocationToAdd + 'AppControlManager.dll')) -ErrorAction Stop - [string]$ValidateAdminCodeSignaturesRegName = 'ValidateAdminCodeSignatures' $ValidateAdminCodeSignaturesRegValue = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name $ValidateAdminCodeSignaturesRegName -ErrorAction SilentlyContinue # This will cause the "A referral was returned from the server." error to show up when AppControl Manager tries to start. @@ -178,8 +156,16 @@ Function AppControl { } catch { Write-Verbose -Message "You can safely ignore this error: $_" } # If this section fails for some reason such as running the script in Windows Sandbox, no error should be thrown - Write-Verbose -Message "Installing AppControl Manager MSIX Package version '$InstallingAppVersion' with architecture '$InstallingAppArchitecture'" - Add-AppPackage -Path $MSIXPath -ForceUpdateFromAnyVersion -DeferRegistrationWhenPackagesAreInUse + Write-Verbose -Message 'Installing the AppControl Manager' + Add-AppPackage -Path $_Package -ForceUpdateFromAnyVersion -DeferRegistrationWhenPackagesAreInUse + + try { + [string]$InstallingAppLocationToAdd = (Get-AppxPackage -Name AppControlManager).InstallLocation + Write-Verbose -Message "Adding the new app's dll and exe (2 files) To the ASR Rules exclusions." + # The cmdlet won't add duplicates + Add-MpPreference -AttackSurfaceReductionOnlyExclusions (Join-Path -Path $InstallingAppLocationToAdd -ChildPath 'AppControlManager.exe'), (Join-Path -Path $InstallingAppLocationToAdd -ChildPath 'AppControlManager.dll') -ErrorAction Stop + } + catch { Write-Verbose -Message "You can safely ignore this error: $_" } } finally { Remove-Item -Path $WorkingDir -Recurse -Force } # Cleaning up the working directory in the TEMP directory } \ No newline at end of file diff --git a/Wiki posts/AppControl Manager/AppControl Manager.md b/Wiki posts/AppControl Manager/AppControl Manager.md index b6a886e47..7792057b7 100644 --- a/Wiki posts/AppControl Manager/AppControl Manager.md +++ b/Wiki posts/AppControl Manager/AppControl Manager.md @@ -228,46 +228,294 @@ AppControl -MSIXPath "Path To the MSIX" -SignTool "Path to signtool.exe" -Verbos You can build the AppControl Manager application directly from the source code locally on your device without using any 3rd party tools in a completely automated way. +It will create the MSIXBundle file containing the X64 and ARM64 MSIX packages. You can even optionally chain it with the [Bootstrapper script](https://github.com/HotCakeX/Harden-Windows-Security/blob/main/Harden-Windows-Security.ps1) to sign and install the application on your system at the end. +
-Click/Tap here to see the PowerShell code +✨ Click/Tap here to see the PowerShell code ✨
```powershell -[System.String]$RepoUrl = 'https://github.com/HotCakeX/Harden-Windows-Security/archive/refs/heads/main.zip' -[System.String]$ZipPath = [System.IO.Path]::Combine($env:TEMP, 'HardenWindowsSecurity.zip') -[System.String]$ExtractPath = $PWD +# Requires -Version 5.1 +# Requires -RunAsAdministrator +$global:ErrorActionPreference = 'Stop' +# Start the stopwatch +$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() +[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 Invoke-WebRequest -Uri $RepoUrl -OutFile $ZipPath -Expand-Archive -Path $ZipPath -DestinationPath $ExtractPath -Force -[System.String]$PathToNavigateTo = [System.IO.Path]::Combine($ExtractPath, 'Harden-Windows-Security-main', 'AppControl Manager') -Set-Location -Path $PathToNavigateTo +Expand-Archive -Path $ZipPath -DestinationPath $InitialWorkingDirectory -Force +Remove-Item -Path $ZipPath -Force +[System.String]$AppControlManagerDirectory = [System.IO.Path]::Combine($InitialWorkingDirectory, "$RepoName-$BranchName", 'AppControl Manager') +Set-Location -Path $AppControlManagerDirectory winget source update 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') } + # Downloads the online installer and automatically runs it and installs the build tools # https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/set-up-your-development-environment # https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools # https://learn.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio # https://learn.microsoft.com/en-us/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.ManagedDesktop --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 --includeRecommended' +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.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' -# Downloads the online installer and automatically runs it and installs the full Windows SDK -winget install --id Microsoft.WindowsSDK.10.0.26100 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget +if ($LASTEXITCODE -ne 0) { throw [System.InvalidOperationException]::New('Failed to install Visual Studio Build Tools') } -winget install --id Microsoft.AppInstaller --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget winget install --id Microsoft.VCRedist.2015+.x64 --exact --accept-package-agreements --accept-source-agreements --uninstall-previous --force --source winget -winget install --id Microsoft.VCRedist.2015+.x86 --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) + ';' + +$Env:Path = [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) + ';' + [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::User) -dotnet build 'AppControl Manager.sln' --configuration Release --verbosity normal /p:Platform=x64 -dotnet msbuild 'AppControl Manager.sln' /p:Configuration=Release /p:AppxPackageDir="MSIXOutput\" /p:GenerateAppxPackageOnBuild=true /p:Platform=x64 -v:normal +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 + Write-Host -Object "Setting environment variable: $name=$value" + } +} + +# 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' +#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' +#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 + +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 ($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 +} + ```