{"record":{"id":"8c12b44d265abdcb","repo":"memstechtips/Winhance","slug":"destination-path-must-include-a-directory","errorCode":null,"errorMessage":"Destination path must include a directory.","messagePattern":"Destination path must include a directory\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Winhance.Infrastructure/Features/AdvancedTools/Services/WimCustomizationService.cs","lineNumber":200,"sourceCode":"        }\r\n        catch (Exception ex)\r\n        {\r\n            _logService.LogError($\"Error adding XML to image: {ex.Message}\", ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    public async Task<string> DownloadUnattendedWinstallXmlAsync(\r\n        string destinationPath,\r\n        IProgress<TaskProgressDetail>? progress = null,\r\n        CancellationToken cancellationToken = default)\r\n    {\r\n        if (string.IsNullOrEmpty(destinationPath))\r\n            throw new ArgumentException(\"Destination path cannot be empty.\", nameof(destinationPath));\r\n\r\n        var destinationDir = _fileSystemService.GetDirectoryName(destinationPath);\r\n        if (string.IsNullOrEmpty(destinationDir))\r\n            throw new ArgumentException(\"Destination path must include a directory.\", nameof(destinationPath));\r\n\r\n        try\r\n        {\r\n            progress?.Report(new TaskProgressDetail\r\n            {\r\n                StatusText = _localization.GetString(\"Progress_DownloadingXml\"),\r\n                TerminalOutput = UnattendedWinstallXmlUrl\r\n            });\r\n\r\n            var xmlContent = await _httpClient.GetStringAsync(UnattendedWinstallXmlUrl, cancellationToken).ConfigureAwait(false);\r\n\r\n            _fileSystemService.CreateDirectory(destinationDir);\r\n            await _fileSystemService.WriteAllTextAsync(destinationPath, xmlContent, cancellationToken).ConfigureAwait(false);\r\n\r\n            progress?.Report(new TaskProgressDetail\r\n            {\r\n                StatusText = _localization.GetString(\"Progress_XmlDownloaded\"),\r\n                TerminalOutput = $\"Saved to: {destinationPath}\"\r","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/memstechtips/Winhance/blob/f23d554eb2d6400b1827bcc46b91294ffa53fbc9/src/Winhance.Infrastructure/Features/AdvancedTools/Services/WimCustomizationService.cs#L182-L218","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Require a fully-qualified path (use a SaveFileDialog with AddExtension and CheckFileDirectory existence) before calling the API.","At the call site, normalize with Path.GetFullPath and assert GetDirectoryName is non-empty.","Default to a known directory (e.g. the ISO working directory) and append the filename, so the directory is always present.","Show the user a validation error in the UI when the chosen path has no directory."],"exampleFix":"// before\nvar destinationDir = _fileSystemService.GetDirectoryName(destinationPath);\nif (string.IsNullOrEmpty(destinationDir))\n    throw new ArgumentException(\"Destination path must include a directory.\", nameof(destinationPath));\n\n// after: normalize first, then the bare-filename case becomes an explicit user error\nvar fullPath = _fileSystemService.GetFullPath(destinationPath);\nvar destinationDir = _fileSystemService.GetDirectoryName(fullPath);\nif (string.IsNullOrEmpty(destinationDir))\n    throw new ArgumentException($\"Destination path '{destinationPath}' has no directory; supply a full path like C:\\\\ISO\\\\autounattend.xml.\", nameof(destinationPath));","handlingStrategy":"validation","validationCode":"var dir = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(destinationPath));\nif (string.IsNullOrEmpty(dir))\n    return; // reject before calling the API","typeGuard":null,"tryCatchPattern":"catch (ArgumentException ex) when (ex.Message.Contains(\"must include a directory\"))\n{\n    // Prompt the user to choose a full path including a folder.\n}","preventionTips":["Normalize the path with Path.GetFullPath before passing it in.","Always build destination paths as Path.Combine(knownDir, fileName) so a directory is guaranteed.","Reject bare filenames in the UI before calling the API."],"tags":["argument-validation","filesystem","path","download","contract"],"backgroundTag":null,"analyzedSha":"f23d554eb2d6400b1827bcc46b91294ffa53fbc9","analyzedAt":"2026-08-13T17:55:20.922Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}