memstechtips/Winhance · error · ArgumentException

Destination path must include a directory.

Error message

Destination path must include a directory.

What it means

ArgumentException thrown by DownloadUnattendedWinstallXmlAsync when destinationPath has no directory component — GetDirectoryName returned empty, meaning the path is a bare filename like "autounattend.xml" with no folder. The method needs a directory to CreateDirectory before writing, so it rejects the path.

Source

Thrown at src/Winhance.Infrastructure/Features/AdvancedTools/Services/WimCustomizationService.cs:200

        }
        catch (Exception ex)
        {
            _logService.LogError($"Error adding XML to image: {ex.Message}", ex);
            return false;
        }
    }

    public async Task<string> DownloadUnattendedWinstallXmlAsync(
        string destinationPath,
        IProgress<TaskProgressDetail>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrEmpty(destinationPath))
            throw new ArgumentException("Destination path cannot be empty.", nameof(destinationPath));

        var destinationDir = _fileSystemService.GetDirectoryName(destinationPath);
        if (string.IsNullOrEmpty(destinationDir))
            throw new ArgumentException("Destination path must include a directory.", nameof(destinationPath));

        try
        {
            progress?.Report(new TaskProgressDetail
            {
                StatusText = _localization.GetString("Progress_DownloadingXml"),
                TerminalOutput = UnattendedWinstallXmlUrl
            });

            var xmlContent = await _httpClient.GetStringAsync(UnattendedWinstallXmlUrl, cancellationToken).ConfigureAwait(false);

            _fileSystemService.CreateDirectory(destinationDir);
            await _fileSystemService.WriteAllTextAsync(destinationPath, xmlContent, cancellationToken).ConfigureAwait(false);

            progress?.Report(new TaskProgressDetail
            {
                StatusText = _localization.GetString("Progress_XmlDownloaded"),
                TerminalOutput = $"Saved to: {destinationPath}"

View on GitHub (pinned to f23d554eb2)

Solutions

  1. Require a fully-qualified path (use a SaveFileDialog with AddExtension and CheckFileDirectory existence) before calling the API.
  2. At the call site, normalize with Path.GetFullPath and assert GetDirectoryName is non-empty.
  3. Default to a known directory (e.g. the ISO working directory) and append the filename, so the directory is always present.
  4. Show the user a validation error in the UI when the chosen path has no directory.

Example fix

// before
var destinationDir = _fileSystemService.GetDirectoryName(destinationPath);
if (string.IsNullOrEmpty(destinationDir))
    throw new ArgumentException("Destination path must include a directory.", nameof(destinationPath));

// after: normalize first, then the bare-filename case becomes an explicit user error
var fullPath = _fileSystemService.GetFullPath(destinationPath);
var destinationDir = _fileSystemService.GetDirectoryName(fullPath);
if (string.IsNullOrEmpty(destinationDir))
    throw new ArgumentException($"Destination path '{destinationPath}' has no directory; supply a full path like C:\\ISO\\autounattend.xml.", nameof(destinationPath));
Defensive patterns

Strategy: validation

Validate before calling

var dir = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(destinationPath));
if (string.IsNullOrEmpty(dir))
    return; // reject before calling the API

Try / catch

catch (ArgumentException ex) when (ex.Message.Contains("must include a directory"))
{
    // Prompt the user to choose a full path including a folder.
}

Prevention

When it happens

Trigger: A caller passes a relative bare filename ("autounattend.xml"), a path with no slash, or a path that GetDirectoryName trims to empty. The downstream CreateDirectory(destinationDir) and FileStream write would target the wrong/relative location, so the guard stops it.

Common situations: User typed just a filename in a save dialog that did not force a folder. A path was built by string concatenation that dropped the directory part. The path was passed through a sanitizer that stripped the folder.

Related errors


AI-assisted analysis of memstechtips/Winhance@f23d554eb2 (2026-08-13). Data as JSON: /api/errors/8c12b44d265abdcb. Report an issue: GitHub.