diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 9f28a6b5b3..8383c0566d 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -158,6 +158,7 @@ ERRORONEXIT errstr ESRB etl +evtx ewgp ewgs execustom @@ -320,6 +321,7 @@ MBH MBs mday mdmp +mdmpto MDs megamorf microsoftentraid @@ -610,6 +612,7 @@ wincodec windir windowsdeveloper winerror +winevt wingdi wingetconfigroot wingetcreate diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d800201612..47e5f598b1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -339,7 +339,7 @@ jobs: - pwsh: | $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") - PsExec -accepteula -s -i $(buildOutDir)\AppInstallerCLITests\AppInstallerCLITests.exe -logto $(artifactsDir)\AICLI-Unpackaged-System.log -s -r junit -o $(artifactsDir)\TEST-AppInstallerCLI-Unpackaged-System.xml + PsExec -accepteula -s -i $(buildOutDir)\AppInstallerCLITests\AppInstallerCLITests.exe -logto $(artifactsDir)\AICLI-Unpackaged-System.log -mdmpto $(artifactsDir)\AICLI-Unpackaged-System.mdmp -s -r junit -o $(artifactsDir)\TEST-AppInstallerCLI-Unpackaged-System.xml displayName: Run Unit Tests Unpackaged Under System Context workingDirectory: '$(buildOutDir)\AppInstallerCLITests' condition: succeededOrFailed() @@ -353,7 +353,7 @@ jobs: displayName: Run Unit Tests Packaged inputs: filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' - arguments: '-Args "~[pips]" -BuildRoot $(buildOutDir) -PackageRoot $(packageLayoutDir) -LogTarget $(artifactsDir)\AICLI-Packaged.log -TestResultsTarget $(artifactsDir)\TEST-AppInstallerCLI-Packaged.xml -ScriptWait' + arguments: '-Args "~[pips]" -BuildRoot $(buildOutDir) -PackageRoot $(packageLayoutDir) -LogTarget $(artifactsDir)\AICLI-Packaged.log -MdmpTarget $(artifactsDir)\AICLI-Packaged.mdmp -TestResultsTarget $(artifactsDir)\TEST-AppInstallerCLI-Packaged.xml -ScriptWait' workingDirectory: 'src' condition: succeededOrFailed() @@ -481,6 +481,34 @@ jobs: - powershell: Get-Process LocalhostWebServer | Stop-Process displayName: Stop LocalhostWebServer condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: 'Copy GA WinGet Log to artifacts folder' + inputs: + targetType: 'inline' + script: | + $source = "$env:LocalAppData\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir" + $destination = "$(artifactsDir)\GA_WinGet_Logs" + if (Test-Path $source) { + Copy-Item -Path $source -Destination $destination -Recurse -Force + } else { + Write-Host "WinGet logs not found at $source" + } + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: 'Copy Application Event Logs to Artifacts' + inputs: + targetType: 'inline' + script: | + $source = "$env:SystemRoot\System32\winevt\Logs\Application.evtx" + $destination = "$(artifactsDir)\Application.evtx" + if (Test-Path $source) { + Copy-Item -Path $source -Destination $destination -Force + } else { + Write-Host "Application event log not found at $source" + } + condition: succeededOrFailed() - task: PublishPipelineArtifact@1 displayName: Publish Pipeline Artifacts diff --git a/src/AppInstallerCLITests/Run-TestsInPackage.ps1 b/src/AppInstallerCLITests/Run-TestsInPackage.ps1 index 9234c9dab2..d471a9859b 100644 --- a/src/AppInstallerCLITests/Run-TestsInPackage.ps1 +++ b/src/AppInstallerCLITests/Run-TestsInPackage.ps1 @@ -12,6 +12,8 @@ location relative to this script. .PARAMETER LogTarget The file path to log to. +.PARAMETER MdmpTarget + The path to write a minidump to if the tests crash. .PARAMETER TestResultsTarget The file path to place the test result file in. .PARAMETER Args @@ -31,6 +33,9 @@ param( [Parameter(Mandatory=$false)] [string]$LogTarget, + [Parameter(Mandatory=$false)] + [string]$MdmpTarget, + [Parameter(Mandatory=$false)] [string]$TestResultsTarget, @@ -93,6 +98,14 @@ if (![String]::IsNullOrEmpty($LogTarget)) Write-Host "Using LogTarget = $LogTarget" } +if (![String]::IsNullOrEmpty($MdmpTarget)) +{ + $Local:temp = Split-Path -Parent $MdmpTarget + $Local:temp = Resolve-Path $Local:temp + $MdmpTarget = Join-Path $Local:temp (Split-Path -Leaf $MdmpTarget) + Write-Host "Using MdmpTarget = $MdmpTarget" +} + if (![String]::IsNullOrEmpty($TestResultsTarget)) { $Local:temp = Split-Path -Parent $TestResultsTarget @@ -123,6 +136,15 @@ else $Local:TestArgs = $Local:TestArgs + " -logto ""$LogTarget""" } +if ([String]::IsNullOrEmpty($MdmpTarget)) +{ + $Local:TestArgs = $Local:TestArgs + " -mdmp" +} +else +{ + $Local:TestArgs = $Local:TestArgs + " -mdmpto ""$MdmpTarget""" +} + if (![String]::IsNullOrEmpty($TestResultsTarget)) { $Local:TestArgs = $Local:TestArgs + " -s -r junit -o ""$TestResultsTarget""" diff --git a/src/AppInstallerCLITests/main.cpp b/src/AppInstallerCLITests/main.cpp index 65a0b1fdaf..0ad079dc45 100644 --- a/src/AppInstallerCLITests/main.cpp +++ b/src/AppInstallerCLITests/main.cpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include #include "TestCommon.h" #include "TestSettings.h" @@ -87,13 +88,14 @@ int main(int argc, char** argv) } else if ("-logto"s == argv[i]) { - ++i; - Logging::FileLogger::Add(std::filesystem::path{ argv[i] }); + if (++i < argc) + { + Logging::FileLogger::Add(std::filesystem::path{ argv[i] }); + } } else if ("-tdd"s == argv[i]) { - ++i; - if (i < argc) + if (++i < argc) { TestCommon::TestDataFile::SetTestDataBasePath(argv[i]); hasSetTestDataBasePath = true; @@ -107,6 +109,17 @@ int main(int argc, char** argv) { keepSQLLogging = true; } + else if ("-mdmp"s == argv[i]) + { + Debugging::EnableSelfInitiatedMinidump(); + } + else if ("-mdmpto"s == argv[i]) + { + if (++i < argc) + { + Debugging::EnableSelfInitiatedMinidump(std::filesystem::path{ argv[i] }); + } + } else { args.push_back(argv[i]); diff --git a/src/AppInstallerCommonCore/Debugging.cpp b/src/AppInstallerCommonCore/Debugging.cpp index e9bc6c6e53..fddac4b3d7 100644 --- a/src/AppInstallerCommonCore/Debugging.cpp +++ b/src/AppInstallerCommonCore/Debugging.cpp @@ -14,17 +14,7 @@ namespace AppInstaller::Debugging struct SelfInitiatedMinidumpHelper { - SelfInitiatedMinidumpHelper() : m_keepFile(false) - { - m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); - m_filePath /= c_minidumpPrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + c_minidumpExtension.data()); - - m_file.reset(CreateFile(m_filePath.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!m_file); - - SetUnhandledExceptionFilter(UnhandledExceptionCallback); - } + SelfInitiatedMinidumpHelper() = default; ~SelfInitiatedMinidumpHelper() { @@ -57,6 +47,30 @@ namespace AppInstaller::Debugging return EXCEPTION_CONTINUE_SEARCH; } + SelfInitiatedMinidumpHelper& Enable(const std::filesystem::path& filePath = {}) + { + std::call_once(m_enableFlag, [&]() + { + if (filePath.empty()) + { + m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); + m_filePath /= c_minidumpPrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + c_minidumpExtension.data()); + } + else + { + m_filePath = filePath; + } + + m_file.reset(CreateFile(m_filePath.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!m_file); + + SetUnhandledExceptionFilter(UnhandledExceptionCallback); + }); + + return *this; + } + void WriteMinidump() { std::thread([&]() { @@ -66,20 +80,25 @@ namespace AppInstaller::Debugging } private: + std::once_flag m_enableFlag; std::filesystem::path m_filePath; wil::unique_handle m_file; - std::atomic_bool m_keepFile; + std::atomic_bool m_keepFile{ false }; }; } void EnableSelfInitiatedMinidump() { - // Force object creation and thus enabling of the crash detection. - SelfInitiatedMinidumpHelper::Instance(); + SelfInitiatedMinidumpHelper::Instance().Enable(); + } + + void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath) + { + SelfInitiatedMinidumpHelper::Instance().Enable(filePath); } void WriteMinidump() { - SelfInitiatedMinidumpHelper::Instance().WriteMinidump(); + SelfInitiatedMinidumpHelper::Instance().Enable().WriteMinidump(); } } diff --git a/src/AppInstallerCommonCore/Public/winget/Debugging.h b/src/AppInstallerCommonCore/Public/winget/Debugging.h index e7c5476f24..d10442ebc9 100644 --- a/src/AppInstallerCommonCore/Public/winget/Debugging.h +++ b/src/AppInstallerCommonCore/Public/winget/Debugging.h @@ -1,12 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once +#include namespace AppInstaller::Debugging { // Enables a self initiated minidump on certain process level failures. + // Only the first call to EnableSelfInitiatedMinidump has any effect. void EnableSelfInitiatedMinidump(); + // Enables a self initiated minidump on certain process level failures. + // Creates the minidump in the given location. + // Only the first call to EnableSelfInitiatedMinidump has any effect. + void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath); + // Forces the minidump to be written. void WriteMinidump(); }