Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@
liwpx
localizationpriority
localsource
Logon

Check warning on line 312 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`Logon` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
logon
LONGLONG
LOWORD
LPARAM
Expand Down Expand Up @@ -587,7 +588,9 @@
Srinivasan
SRL
srs
STARTF
STARTUPINFOEX
STARTUPINFOW
STDMETHODCALLTYPE
storeapps
storeorigin
Expand Down Expand Up @@ -658,6 +661,7 @@
upgradecode
URLZONE
USEDEFAULT
USESHOWWINDOW
userfilesetting
userprofile
uswgp
Expand Down
18 changes: 18 additions & 0 deletions src/AppInstallerCLICore/Workflows/UninstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,24 @@ namespace AppInstaller::CLI::Workflow
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED);
}

// In non-interactive sessions (Session 0, SSH, WinRM, CI), the AppX deployment broker
// requires an interactive logon session when invoked from a packaged context, returning
// ERROR_NO_SUCH_LOGON_SESSION (0x80070520). If the package is not registered for the user
// (e.g. removed or absent), avoid reporting a false failure.
if (re.GetErrorCode() == HRESULT_FROM_WIN32(ERROR_NO_SUCH_LOGON_SESSION))
{
if (!packageFamilyName.empty() && !Deployment::IsRegistered(packageFamilyName))
{
AICLI_LOG(CLI, Info, << "Package " << packageFamilyName << " is not registered for user; ignoring ERROR_NO_SUCH_LOGON_SESSION (0x80070520)");
continue;
}

if (!Runtime::IsRunningAsInteractiveUser() || Runtime::IsRunningInPackagedContext())
{
AICLI_LOG(CLI, Warning, << "Non-interactive MSIX uninstall encountered ERROR_NO_SUCH_LOGON_SESSION (0x80070520) in packaged context for " << packageFamilyName);
}
}

context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode());
context << ReportUninstallerResult("MSIXUninstall"sv, re.GetErrorCode(), /* isHResult */ true);
return;
Expand Down
18 changes: 18 additions & 0 deletions src/AppInstallerCLITests/UninstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,21 @@ TEST_CASE("UninstallFlow_AdminContextWithUserScopeInstall", "[UninstallFlow][wor

REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED);
}

TEST_CASE("UninstallFlow_MSIXUninstall_NonInteractiveSessionNoError", "[UninstallFlow][workflow]")
{
std::ostringstream uninstallOutput;
TestContext context{ uninstallOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();

OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix }));
OverrideForMSIXUninstall(context);
context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query);

UninstallCommand uninstall({});
uninstall.Execute(context);
INFO(uninstallOutput.str());

REQUIRE(!context.IsTerminated());
}

71 changes: 70 additions & 1 deletion src/AppInstallerCommonCore/Deployment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,43 @@ namespace AppInstaller::Deployment
return registrationDeferred;
}

bool RemovePackageUnpackaged(std::string_view packageFullName)
{
AICLI_LOG(Core, Info, << "Attempting unpackaged Remove-AppxPackage fallback for: " << packageFullName);

std::wstring commandLine = L"powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -Command \"Remove-AppxPackage -Package '" + Utility::ConvertToUTF16(packageFullName) + L"'\"";

STARTUPINFOW startupInfo = { sizeof(startupInfo) };
startupInfo.dwFlags = STARTF_USESHOWWINDOW;
startupInfo.wShowWindow = SW_HIDE;
wil::unique_process_information processInfo;

if (!CreateProcessW(nullptr, &commandLine[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &processInfo))
{
AICLI_LOG(Core, Warning, << "Failed to create PowerShell process for Remove-AppxPackage fallback: " << GetLastError());
return false;
}

DWORD waitResult = WaitForSingleObject(processInfo.hProcess, 60000);
if (waitResult == WAIT_OBJECT_0)
{
DWORD exitCode = 0;
if (GetExitCodeProcess(processInfo.hProcess, &exitCode) && exitCode == 0)
{
AICLI_LOG(Core, Info, << "Unpackaged Remove-AppxPackage succeeded for: " << packageFullName);
return true;
}
AICLI_LOG(Core, Warning, << "Unpackaged Remove-AppxPackage returned non-zero exit code: " << exitCode);
}
else
{
AICLI_LOG(Core, Warning, << "Unpackaged Remove-AppxPackage wait failed or timed out: " << waitResult);
TerminateProcess(processInfo.hProcess, 1);
}

return false;
}

void RemovePackage(
std::string_view packageFullName,
RemovalOptions options,
Expand All @@ -276,7 +313,39 @@ namespace AppInstaller::Deployment
winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str();
auto deployOperation = packageManager.RemovePackageAsync(fullName, options);

WaitForDeployment(deployOperation, id, callback);
HRESULT hr = WaitForDeployment(deployOperation, id, callback, false);
if (!SUCCEEDED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_NO_SUCH_LOGON_SESSION))
{
AICLI_LOG(Core, Warning, << "RemovePackage operation #" << id << " received ERROR_NO_SUCH_LOGON_SESSION (0x80070520) in non-interactive session for " << packageFullName);

std::string packageFamilyName = Msix::GetPackageFamilyNameFromFullName(std::string{ packageFullName });
if (!packageFamilyName.empty() && !IsRegistered(packageFamilyName))
{
AICLI_LOG(Core, Info, << "Package " << packageFamilyName << " is not registered for current user, treating as successful removal");
return;
}

// If running in packaged context or non-interactive session, fall back to unpackaged removal
if (!Runtime::IsRunningAsInteractiveUser() || Runtime::IsRunningInPackagedContext())
{
if (RemovePackageUnpackaged(packageFullName))
{
return;
}

// Check again if the package registration was removed
if (!packageFamilyName.empty() && !IsRegistered(packageFamilyName))
{
AICLI_LOG(Core, Info, << "Package " << packageFamilyName << " is no longer registered after fallback attempt");
return;
}
}
}

THROW_IF_FAILED(hr);
}
}

bool AddPackageMachineScope(
Expand Down
Loading