fix version and win10 start error

This commit is contained in:
Leilei Zhang
2025-10-14 22:13:07 +08:00
parent ae32a7f173
commit ec67e8feee
4 changed files with 245 additions and 98 deletions

View File

@@ -9,7 +9,7 @@ Param(
[Parameter(Mandatory=$false)]
[ValidateSet("Debug", "Release")]
[string]$Configuration = "Release",
[switch]$Clean,
[switch]$ForceCert,
[switch]$NoSign
@@ -45,6 +45,7 @@ function Find-WindowsSDKTool {
# Simple fallback: check common Windows SDK locations
$commonPaths = @(
"${env:ProgramFiles}\Windows Kits\10\bin\*\$Architecture\$ToolName",
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\$Architecture\$ToolName",
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x86\$ToolName" # SignTool fallback
)
@@ -62,12 +63,12 @@ function Find-WindowsSDKTool {
}
function Test-CertificateValidity {
param([string]$PfxPath, [string]$PasswordFile)
param([string]$PfxPath, [string]$SecretFilePath)
if (-not (Test-Path $PfxPath) -or -not (Test-Path $PasswordFile)) { return $false }
if (-not (Test-Path $PfxPath) -or -not (Test-Path $SecretFilePath)) { return $false }
try {
$password = (Get-Content $PasswordFile -Raw).Trim()
$password = (Get-Content $SecretFilePath -Raw).Trim()
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($PfxPath, $password)
$isValid = $cert.HasPrivateKey -and $cert.NotAfter -gt (Get-Date)
$cert.Dispose()
@@ -131,7 +132,7 @@ try {
Get-ChildItem "Cert:\CurrentUser\My" -ErrorAction SilentlyContinue | Out-Null
}
} catch {
Write-BuildLog "Note: Certificate provider setup may need manual configuration: $_" -Level Warning
Write-BuildLog ("Note: Certificate provider setup may need manual configuration: {0}" -f $_) -Level Warning
}
# Project root folder (now set to current script folder for local builds)
@@ -163,7 +164,7 @@ if ($ForceCert -and (Test-Path $UserFolder)) {
}
# Ensure dev cert (development only; not for production use) - skip if NoSign specified
$needNewCert = -not $NoSign -and (-not (Test-Path $CertPfxFile) -or $ForceCert -or -not (Test-CertificateValidity -PfxPath $CertPfxFile -PasswordFile $CertPwdFile))
$needNewCert = -not $NoSign -and (-not (Test-Path $CertPfxFile) -or $ForceCert -or -not (Test-CertificateValidity -PfxPath $CertPfxFile -SecretFilePath $CertPwdFile))
if ($needNewCert) {
Write-BuildLog "Generating development certificate (prefix=$($script:Config.CertPrefix))..." -Level Info
@@ -212,6 +213,30 @@ $sparseDir = $PSScriptRoot
$manifestPath = Join-Path $sparseDir 'AppxManifest.xml'
if (-not (Test-Path $manifestPath)) { throw "Missing AppxManifest.xml in PackageIdentity folder: $manifestPath" }
$versionPropsPath = Join-Path $PowerToysRoot 'src\Version.props'
$targetManifestVersion = $null
$versionCandidate = $null
if (Test-Path $versionPropsPath) {
try {
[xml]$propsXml = Get-Content -Path $versionPropsPath -Raw
$versionCandidate = $propsXml.Project.PropertyGroup.Version
} catch {
Write-BuildLog ("Unable to read version from {0}: {1}" -f $versionPropsPath, $_) -Level Warning
}
} else {
Write-BuildLog "Version.props not found at $versionPropsPath; manifest version will remain unchanged." -Level Warning
}
if ($versionCandidate) {
$targetManifestVersion = $versionCandidate.Trim()
if (($targetManifestVersion -split '\.').Count -lt 4) {
$targetManifestVersion = "$targetManifestVersion.0"
}
Write-BuildLog "Using sparse package version from Version.props: $targetManifestVersion" -Level Info
} else {
Write-BuildLog "No version value provided; manifest version will remain unchanged." -Level Info
}
# Find MakeAppx.exe from Windows SDK
try {
$makeAppxPath = Find-WindowsSDKTool -ToolName "makeappx.exe" -Architecture $Platform
@@ -230,7 +255,7 @@ if (Test-Path $msixPath) {
Remove-Item $msixPath -Force -ErrorAction Stop
Write-BuildLog "Successfully removed existing MSIX file" -Level Success
} catch {
Write-BuildLog "Warning: Could not remove existing MSIX file: $_" -Level Warning
Write-BuildLog ("Warning: Could not remove existing MSIX file: {0}" -f $_) -Level Warning
}
}
@@ -277,6 +302,37 @@ try {
}
}
# Ensure publisher matches the dev certificate for local builds
$manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml'
$shouldUseDevPublisher = $env:CIBuild -ne 'true'
if (Test-Path $manifestStagingPath) {
try {
[xml]$manifestXml = Get-Content -Path $manifestStagingPath -Raw
$identityNode = $manifestXml.Package.Identity
$manifestChanged = $false
if ($identityNode) {
if ($targetManifestVersion -and $identityNode.Version -ne $targetManifestVersion) {
Write-BuildLog "Updating manifest version to $targetManifestVersion" -Level Info
$identityNode.SetAttribute('Version', $targetManifestVersion)
$manifestChanged = $true
}
if ($shouldUseDevPublisher -and $identityNode.Publisher -ne $script:Config.CertSubject) {
Write-BuildLog "Updating manifest publisher for local build" -Level Warning
$identityNode.SetAttribute('Publisher', $script:Config.CertSubject)
$manifestChanged = $true
}
}
if ($manifestChanged) {
$manifestXml.Save($manifestStagingPath)
}
} catch {
Write-BuildLog ("Unable to adjust manifest metadata: {0}" -f $_) -Level Warning
}
}
Write-BuildLog "Staging directory prepared with essential files only" -Level Success
# Pack MSIX using staging directory
@@ -297,7 +353,7 @@ try {
Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
Write-BuildLog "Cleaned up staging directory" -Level Info
} catch {
Write-BuildLog "Warning: Could not clean up staging directory: $_" -Level Warning
Write-BuildLog ("Warning: Could not clean up staging directory: {0}" -f $_) -Level Warning
}
}
}
@@ -332,5 +388,5 @@ if ($NoSign) {
}
Write-BuildLog "Register sparse package:" -Level Info
Write-BuildLog " Add-AppxPackage -Register `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning

View File

@@ -0,0 +1,43 @@
<#
.SYNOPSIS
Determine whether a given process (by PID) runs with an MSIX/UWP package identity.
.DESCRIPTION
Calls the Windows API GetPackageFullName to check if the target process executes under an MSIX/Sparse App/UWP package identity.
Returns the package full name when identity is present, or "No package identity" otherwise.
.PARAMETER ProcessId
The process ID to inspect.
.EXAMPLE
.\Check-ProcessIdentity.ps1 -pid 12345
#>
param(
[Parameter(Mandatory=$true)]
[int]$ProcessId
)
Add-Type -TypeDefinition @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class P {
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenProcess(uint a, bool b, int p);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr h);
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern int GetPackageFullName(IntPtr h, ref int l, StringBuilder b);
public static string G(int pid) {
IntPtr h = OpenProcess(0x1000, false, pid);
if (h == IntPtr.Zero) return "Failed to open process";
int len = 0;
GetPackageFullName(h, ref len, null);
if (len == 0) { CloseHandle(h); return "No package identity"; }
var sb = new StringBuilder(len);
int r = GetPackageFullName(h, ref len, sb);
CloseHandle(h);
return r == 0 ? sb.ToString() : "Error:" + r;
}
}
'@
$result = [P]::G($ProcessId)
Write-Output $result

View File

@@ -15,7 +15,7 @@
<NoSignParam Condition="'$(Configuration)' != 'Debug' AND '$(NoSignCI)' != 'true'"></NoSignParam>
</PropertyGroup>
<Exec Command="powershell -NonInteractive -ExecutionPolicy Bypass -File &quot;$(MSBuildThisFileDirectory)BuildSparsePackage.ps1&quot; -Platform $(Platform) -Configuration $(Configuration) $(NoSignParam)"
<Exec Command="powershell -NonInteractive -ExecutionPolicy Bypass -File &quot;$(MSBuildThisFileDirectory)BuildSparsePackage.ps1&quot; -Platform $(Platform) -Configuration $(Configuration) $(NoSignParam)"
ContinueOnError="false"
WorkingDirectory="$(MSBuildThisFileDirectory)" />
</Target>
@@ -94,6 +94,7 @@
<None Include="AppxManifest.xml" />
<None Include="BuildSparsePackage.ps1" />
<None Include="BuildSparsePackage.cmd" />
<None Include="Check-ProcessIdentity.ps1" />
</ItemGroup>
<ItemGroup>

View File

@@ -101,16 +101,16 @@ HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu,
AssocGetPerceivedType(pszExt, &type, &flag, NULL);
free(pszPath);
bool dragDropFlag = false;
// If selected file is an image...
if (type == PERCEIVED_TYPE_IMAGE)
{
HRESULT hr = E_UNEXPECTED;
wchar_t strResizePictures[128] = { 0 };
// If handling drag-and-drop...
wchar_t strResizePictures[128] = {};
if (m_pidlFolder)
{
dragDropFlag=true;
dragDropFlag = true;
wcscpy_s(strResizePictures, ARRAYSIZE(strResizePictures), context_menu_caption_here.c_str());
}
else
@@ -118,13 +118,15 @@ HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu,
wcscpy_s(strResizePictures, ARRAYSIZE(strResizePictures), context_menu_caption.c_str());
}
MENUITEMINFO mii;
MENUITEMINFO mii{};
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE;
mii.wID = idCmdFirst + ID_RESIZE_PICTURES;
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
mii.fType = MFT_STRING;
mii.dwTypeData = (PWSTR)strResizePictures;
mii.wID = idCmdFirst + ID_RESIZE_PICTURES;
mii.dwTypeData = strResizePictures;
mii.cch = ARRAYSIZE(strResizePictures);
mii.fState = MFS_ENABLED;
HICON hIcon = static_cast<HICON>(LoadImage(g_hInst_imageResizer, MAKEINTRESOURCE(IDI_RESIZE_PICTURES), IMAGE_ICON, 16, 16, 0));
if (hIcon)
{
@@ -139,21 +141,14 @@ HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu,
if (dragDropFlag)
{
// Insert the menu entry at indexMenu+1 since the first entry should be "Copy here"
indexMenu++;
}
else
{
// indexMenu gets the first possible menu item index based on the location of the shellex registry key.
// If the registry entry is under SystemFileAssociations for the image formats, ShellImagePreview (in Windows by default) will be at indexMenu=0
// Shell ImagePreview consists of 4 menu items, a separator, Rotate right, Rotate left, and another separator
// Check if the entry at indexMenu is a separator, insert the new menu item at indexMenu+1 if true
MENUITEMINFO miiExisting;
miiExisting.dwTypeData = NULL;
miiExisting.fMask = MIIM_TYPE;
MENUITEMINFO miiExisting{};
miiExisting.cbSize = sizeof(MENUITEMINFO);
GetMenuItemInfo(hmenu, indexMenu, TRUE, &miiExisting);
if (miiExisting.fType == MFT_SEPARATOR)
miiExisting.fMask = MIIM_TYPE;
if (GetMenuItemInfo(hmenu, indexMenu, TRUE, &miiExisting) && miiExisting.fType == MFT_SEPARATOR)
{
indexMenu++;
}
@@ -173,6 +168,7 @@ HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu,
{
hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
return hr;
}
@@ -235,112 +231,147 @@ HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici, IShellIte
{
// Set the application path based on the location of the dll
std::wstring path = get_module_folderpath(g_hInst_imageResizer);
path = path + L"\\PowerToys.ImageResizer.exe";
LPTSTR lpApplicationName = &path[0];
// Create an anonymous pipe to stream filenames
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe;
HANDLE hWritePipe;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
path += L"\\PowerToys.ImageResizer.exe";
LPTSTR lpApplicationName = path.data();
HRESULT hr = E_FAIL;
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
GUID pipeGuid{};
if (FAILED(CoCreateGuid(&pipeGuid)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT, 0))
wchar_t guidBuffer[64] = {};
StringFromGUID2(pipeGuid, guidBuffer, ARRAYSIZE(guidBuffer));
std::wstring pipeName = L"\\\\.\\pipe\\PowerToysImageResizer_";
std::wstring guidString(guidBuffer);
if (guidString.length() > 2)
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
pipeName.append(guidString.substr(1, guidString.length() - 2));
}
else
{
pipeName.append(guidString);
}
HANDLE hNamedPipe = CreateNamedPipeW(
pipeName.c_str(),
PIPE_ACCESS_OUTBOUND,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
0,
0,
0,
nullptr);
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
return HRESULT_FROM_WIN32(GetLastError());
}
CAtlFile writePipe(hWritePipe);
CString commandLine;
commandLine.Format(_T("\"%s\""), lpApplicationName);
CString arguments;
// Set the output directory
if (m_pidlFolder)
{
TCHAR szFolder[MAX_PATH];
SHGetPathFromIDList(m_pidlFolder, szFolder);
commandLine.AppendFormat(_T(" /d \"%s\""), szFolder);
if (SHGetPathFromIDList(m_pidlFolder, szFolder))
{
arguments.AppendFormat(_T("/d \"%s\""), szFolder);
}
}
int nSize = commandLine.GetLength() + 1;
LPTSTR lpszCommandLine = new TCHAR[nSize];
_tcscpy_s(lpszCommandLine, nSize, commandLine);
CString pipeArgument(pipeName.c_str());
if (!arguments.IsEmpty())
{
arguments.Append(_T(" "));
}
arguments.Append(pipeArgument);
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
if (!arguments.IsEmpty())
{
commandLine.Append(_T(" "));
commandLine.Append(arguments);
}
STARTUPINFO startupInfo{};
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.hStdInput = hReadPipe;
startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (pici)
{
startupInfo.wShowWindow = static_cast<WORD>(pici->nShow);
}
else
{
startupInfo.wShowWindow = SW_SHOWNORMAL;
}
startupInfo.dwFlags = STARTF_USESHOWWINDOW;
startupInfo.wShowWindow = pici ? static_cast<WORD>(pici->nShow) : SW_SHOWNORMAL;
PROCESS_INFORMATION processInformation;
PROCESS_INFORMATION processInformation{};
bool launchSucceeded = false;
// Try MSIX sparse package first
std::wstring packageFamilyName;
#if !defined(CIBUILD)
// Non-CI Release build
packageFamilyName = L"djwsxzxb4ksa8";
packageFamilyName = L"djwsxzxb4ksa8";
#else
// Debug build or CI build
packageFamilyName = L"8wekyb3d8bbwe";
packageFamilyName = L"8wekyb3d8bbwe";
#endif
std::wstring aumidTarget = L"shell:AppsFolder\\Microsoft.PowerToys.SparseApp_" + packageFamilyName + L"!PowerToys.ImageResizerUI";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
sei.fMask = SEE_MASK_FLAG_NO_UI;
sei.lpVerb = L"open";
sei.lpFile = aumidTarget.c_str();
sei.lpParameters = lpszCommandLine + wcslen(lpApplicationName) + 3; // Skip the exe path and quotes
sei.lpParameters = arguments.IsEmpty() ? nullptr : arguments.GetString();
sei.nShow = pici ? static_cast<WORD>(pici->nShow) : SW_SHOWNORMAL;
if (ShellExecuteExW(&sei) && reinterpret_cast<INT_PTR>(sei.hInstApp) > 32)
{
// MSIX launch succeeded, use existing pipe communication
// (no changes to the rest of the function)
delete[] lpszCommandLine;
launchSucceeded = true;
}
else
{
// Fallback to traditional exe
// Start the resizer
CreateProcess(
NULL,
lpszCommandLine,
NULL,
NULL,
TRUE,
LPWSTR mutableCommandLine = commandLine.GetBuffer();
BOOL created = CreateProcess(
nullptr,
mutableCommandLine,
nullptr,
nullptr,
FALSE,
0,
NULL,
NULL,
nullptr,
nullptr,
&startupInfo,
&processInformation);
delete[] lpszCommandLine;
if (!CloseHandle(processInformation.hProcess))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!CloseHandle(processInformation.hThread))
commandLine.ReleaseBuffer();
if (!created)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hNamedPipe);
return hr;
}
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
launchSucceeded = true;
}
if (!launchSucceeded)
{
CloseHandle(hNamedPipe);
return E_FAIL;
}
BOOL connected = ConnectNamedPipe(hNamedPipe, nullptr);
if (!connected && GetLastError() != ERROR_PIPE_CONNECTED)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hNamedPipe);
return hr;
}
CAtlFile writePipe;
writePipe.Attach(hNamedPipe);
hNamedPipe = INVALID_HANDLE_VALUE;
// psiItemArray is NULL if called from InvokeCommand. This part is used for the MSI installer. It is not NULL if it is called from Invoke (MSIX).
if (!psiItemArray)
{
@@ -351,33 +382,49 @@ HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici, IShellIte
CString fileName(i.CurrentItem());
fileName.Append(_T("\r\n"));
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
hr = writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
if (FAILED(hr))
{
writePipe.Close();
return hr;
}
}
}
else
{
//m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
// m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
DWORD fileCount = 0;
// Gets the list of files currently selected using the IShellItemArray
psiItemArray->GetCount(&fileCount);
// Iterate over the list of files
for (DWORD i = 0; i < fileCount; i++)
{
IShellItem* shellItem;
psiItemArray->GetItemAt(i, &shellItem);
LPWSTR itemName;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
CString fileName(itemName);
fileName.Append(_T("\r\n"));
// Write the file path into the input stream for image resizer
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
hr = writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
if (FAILED(hr))
{
writePipe.Close();
CoTaskMemFree(itemName);
shellItem->Release();
return hr;
}
CoTaskMemFree(itemName);
shellItem->Release();
}
}
hr = writePipe.Flush();
if (FAILED(hr))
{
writePipe.Close();
return hr;
}
writePipe.Close();
hr = S_OK;
return hr;
return S_OK;
}
HRESULT __stdcall CContextMenuHandler::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName)