Skip to content

WinGetSourceCreator: CopyManifestFile does not create destination subdirectories (re-file of #4181, unaddressed ~2 years) #6426

Description

@JohnnyElvis

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:

  1. Build IndexCreationTool.exe.
  2. Copy a nested manifest tree (e.g. manifests/7/7zip/7zip/22.00/...) into a LocalManifests folder.
  3. Run IndexCreationTool.exe -f source.json.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Possible-DuplicateAutomatically surfaced as a likely duplicate of an existing issue; awaiting maintainer confirmation.Resolution-DuplicateIssue is a duplicate

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions