Summary
WinGetSourceCreator fails to create local winget source packages when the manifest tree contains subdirectories, because CopyManifestFile / CopyManifestFiles in src/WinGetSourceCreator/WinGetLocalSource.cs write manifest files to the working directory without first creating the destination sub-directory. File.WriteAllText(destinationFile, content) then throws:
Could not find a part of the path 'C:\...\WorkDir\7\7zip\7zip\22.00\7zip.7zip.installer.yaml'.
This is a re-file of #4181, which I originally opened on 20 Feb 2024. A maintainer confirmed the repro on 12 Mar 2024 ("I'll work on a fix sometime later this month") and it has since gone ~2 years with no fix and no attached PR (Development: No branches or pull requests), despite multiple follow-ups from me and a second affected user. I'm opening this as a focused, single-action-point issue with the concrete fix inline so it can be actioned directly.
Why this matters (air-gapped / regulated environments)
Building a pre-indexed offline winget source (source.msix + index.db) via IndexCreationTool / WinGetSourceCreator is the only supported way to run winget in air-gapped, heavily regulated environments (finance, gaming/gambling, defense, healthcare) where endpoints cannot reach cdn.winget.microsoft.com or any public REST source. In these environments a private, signed, pre-indexed package is a hard compliance requirement — not a convenience. This bug breaks the primary tool Microsoft ships for that exact scenario the moment a real manifest tree (which is inherently nested: <first-letter>/<publisher>/<package>/<version>/) is used.
Repro
Same as #4181:
- Build
IndexCreationTool.exe.
- Copy a nested manifest tree (e.g.
manifests/7/7zip/7zip/22.00/...) into a LocalManifests folder.
- Run
IndexCreationTool.exe -f source.json.
- Fails with
Could not find a part of the path ....
Root cause
CopyManifestFiles recurses into Path.Combine(destDir, subdir.Name) but the nested destination directory is never created before CopyManifestFile calls File.WriteAllText.
Proposed fix
In CopyManifestFile (src/WinGetSourceCreator/WinGetLocalSource.cs), create the destination directory before writing:
// Copies a file and replaces any token found.
private void CopyManifestFile(string sourceFile, string destinationFile)
{
if (!File.Exists(sourceFile))
{
throw new FileNotFoundException(sourceFile);
}
var content = File.ReadAllText(sourceFile);
foreach (var token in this.tokens.Tokens)
{
if (content.Contains(token.Key))
{
content = content.Replace(token.Key, token.Value);
}
}
string? directoryPath = Path.GetDirectoryName(destinationFile);
if (directoryPath != null && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
File.WriteAllText(destinationFile, content);
}
I'm happy to open a PR with this change if a maintainer confirms they'll review it — the fix is a few lines and has been sitting in #4181 since Oct 2025.
Environment
- Windows 10 / 11
winget-cli master
- Component:
src/WinGetSourceCreator (used by IndexCreationTool)
Related: #4181
Summary
WinGetSourceCreatorfails to create local winget source packages when the manifest tree contains subdirectories, becauseCopyManifestFile/CopyManifestFilesinsrc/WinGetSourceCreator/WinGetLocalSource.cswrite manifest files to the working directory without first creating the destination sub-directory.File.WriteAllText(destinationFile, content)then throws:This is a re-file of #4181, which I originally opened on 20 Feb 2024. A maintainer confirmed the repro on 12 Mar 2024 ("I'll work on a fix sometime later this month") and it has since gone ~2 years with no fix and no attached PR (
Development: No branches or pull requests), despite multiple follow-ups from me and a second affected user. I'm opening this as a focused, single-action-point issue with the concrete fix inline so it can be actioned directly.Why this matters (air-gapped / regulated environments)
Building a pre-indexed offline winget source (
source.msix+index.db) viaIndexCreationTool/WinGetSourceCreatoris the only supported way to run winget in air-gapped, heavily regulated environments (finance, gaming/gambling, defense, healthcare) where endpoints cannot reachcdn.winget.microsoft.comor any public REST source. In these environments a private, signed, pre-indexed package is a hard compliance requirement — not a convenience. This bug breaks the primary tool Microsoft ships for that exact scenario the moment a real manifest tree (which is inherently nested:<first-letter>/<publisher>/<package>/<version>/) is used.Repro
Same as #4181:
IndexCreationTool.exe.manifests/7/7zip/7zip/22.00/...) into aLocalManifestsfolder.IndexCreationTool.exe -f source.json.Could not find a part of the path ....Root cause
CopyManifestFilesrecurses intoPath.Combine(destDir, subdir.Name)but the nested destination directory is never created beforeCopyManifestFilecallsFile.WriteAllText.Proposed fix
In
CopyManifestFile(src/WinGetSourceCreator/WinGetLocalSource.cs), create the destination directory before writing:I'm happy to open a PR with this change if a maintainer confirms they'll review it — the fix is a few lines and has been sitting in #4181 since Oct 2025.
Environment
winget-climastersrc/WinGetSourceCreator(used byIndexCreationTool)Related: #4181