From 5749550de3c286b436ee4b7282bca3b09785850b Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 10 Oct 2025 13:05:51 -0500 Subject: [PATCH 1/4] Allow exporting to hidden files --- .../Workflows/ImportExportFlow.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index f6d8bf8ec1..3a6edc0e02 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -157,8 +157,25 @@ namespace AppInstaller::CLI::Workflow auto packages = PackagesJson::CreateJson(context.Get()); std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; + + // Check if the file exists and is hidden + DWORD attrs = std::filesystem::exists(outputFilePath) ? GetFileAttributesW(outputFilePath.c_str()) : INVALID_FILE_ATTRIBUTES; + bool isHidden = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_HIDDEN)); + + if (isHidden) + { + // Remove hidden attribute so we can write to it + SetFileAttributesW(outputFilePath.c_str(), attrs & ~FILE_ATTRIBUTE_HIDDEN); + } + std::ofstream outputFileStream{ outputFilePath }; outputFileStream << packages; + + if (isHidden) + { + // Restore hidden attribute + SetFileAttributesW(outputFilePath.c_str(), attrs); + } } void ReadImportFile(Execution::Context& context) From af5e1f549e85e3df3389c1cf5825db449fa7557c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 14 Oct 2025 20:32:35 -0500 Subject: [PATCH 2/4] Update release notes --- doc/ReleaseNotes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index cf22c48572..989d7ea875 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -39,3 +39,6 @@ The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` enviro ## Bug Fixes + +## Bug Fixes +* `winget export` now works when the destination path is a hidden file From 1a3704f4d851a5499abd3a6899c365df0a1410ba Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 26 Mar 2026 22:50:21 -0500 Subject: [PATCH 3/4] Write to file without changing attributes --- doc/ReleaseNotes.md | 3 -- .../Workflows/ImportExportFlow.cpp | 29 +++++++++---------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 989d7ea875..33792b1cf4 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -38,7 +38,4 @@ The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` enviro ## Bug Fixes - - -## Bug Fixes * `winget export` now works when the destination path is a hidden file diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index 3a6edc0e02..c426dc218a 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -158,24 +158,21 @@ namespace AppInstaller::CLI::Workflow std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; - // Check if the file exists and is hidden - DWORD attrs = std::filesystem::exists(outputFilePath) ? GetFileAttributesW(outputFilePath.c_str()) : INVALID_FILE_ATTRIBUTES; + // GetFileAttributesW returns INVALID_FILE_ATTRIBUTES for non-existent files, so no separate exists() check is needed. + DWORD attrs = GetFileAttributesW(outputFilePath.c_str()); bool isHidden = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_HIDDEN)); - if (isHidden) - { - // Remove hidden attribute so we can write to it - SetFileAttributesW(outputFilePath.c_str(), attrs & ~FILE_ATTRIBUTE_HIDDEN); - } - - std::ofstream outputFileStream{ outputFilePath }; - outputFileStream << packages; - - if (isHidden) - { - // Restore hidden attribute - SetFileAttributesW(outputFilePath.c_str(), attrs); - } + // Open the file directly without changing its attributes: + // - For an existing hidden file, use TRUNCATE_EXISTING to clear its content while preserving its attributes. + // - Otherwise, use CREATE_ALWAYS to create a new file or overwrite an existing one. + DWORD creationDisposition = isHidden ? TRUNCATE_EXISTING : CREATE_ALWAYS; + wil::unique_hfile fileHandle{ CreateFileW(outputFilePath.c_str(), GENERIC_WRITE, 0, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr) }; + THROW_LAST_ERROR_IF(!fileHandle); + + Json::StreamWriterBuilder writerBuilder; + std::string jsonContent = Json::writeString(writerBuilder, packages); + DWORD bytesWritten = 0; + THROW_LAST_ERROR_IF(!WriteFile(fileHandle.get(), jsonContent.c_str(), static_cast(jsonContent.size()), &bytesWritten, nullptr)); } void ReadImportFile(Execution::Context& context) From c71b30207580d2591224fb4ba08ce2b6f64d4c62 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 26 Mar 2026 22:54:30 -0500 Subject: [PATCH 4/4] Spelling --- src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index c426dc218a..14a23d21e5 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -158,7 +158,7 @@ namespace AppInstaller::CLI::Workflow std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; - // GetFileAttributesW returns INVALID_FILE_ATTRIBUTES for non-existent files, so no separate exists() check is needed. + // GetFileAttributesW returns INVALID_FILE_ATTRIBUTES for nonexistent files, so no separate exists() check is needed. DWORD attrs = GetFileAttributesW(outputFilePath.c_str()); bool isHidden = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_HIDDEN));