diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index cf22c48572..33792b1cf4 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -38,4 +38,4 @@ The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` enviro ## 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 f6d8bf8ec1..14a23d21e5 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -157,8 +157,22 @@ namespace AppInstaller::CLI::Workflow auto packages = PackagesJson::CreateJson(context.Get()); std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; - std::ofstream outputFileStream{ outputFilePath }; - outputFileStream << packages; + + // 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)); + + // 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)