diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 63f59bbe5c..03a7ed2f71 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -357,6 +357,7 @@ testsettingname TEXTFORMAT TEXTINCLUDE threadpool +TOCTOU tpl TRACELOGGING triaged diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index fd13ccda6e..e2240e0aed 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -26,6 +26,7 @@ apfn apicontract apiset appdata +APPEXECLINK appinstallertest applic appname @@ -192,6 +193,7 @@ FONTHASH FORPARSING foundfr fsanitize +Fsctl FULLMUTEX FULLWIDTH fundraiser @@ -531,6 +533,7 @@ SETTINGMAPPING sev sfs sfsclient +SGNR SHCONTF shellapi SHGDN @@ -638,6 +641,7 @@ vns vscodeignore vsconfig vstest +vswhere waitable wal wcex @@ -665,6 +669,7 @@ wingetutil winreg winrtact winstring +wintrust WMI wmmc wnd @@ -680,6 +685,7 @@ wsb wsl wsv wto +WVT wwinmain WZDNCRFJ xcopy diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index 2b59deca7d..41f647c2f2 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -1,235 +1,239 @@ ---- -title: Exit Codes -description: WinGet return codes and their meanings -ms.date: 05/02/2023 -ms.topic: article -ms.localizationpriority: medium ---- - -# Return Codes - -## General Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150001 | -1978335231 | APPINSTALLER_CLI_ERROR_INTERNAL_ERROR | Internal Error | -| 0x8A150002 | -1978335230 | APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS | Invalid command line arguments | -| 0x8A150003 | -1978335229 | APPINSTALLER_CLI_ERROR_COMMAND_FAILED | Executing command failed | -| 0x8A150004 | -1978335228 | APPINSTALLER_CLI_ERROR_MANIFEST_FAILED | Opening manifest failed | -| 0x8A150005 | -1978335227 | APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED | Cancellation signal received | -| 0x8A150006 | -1978335226 | APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED | Running ShellExecute failed | -| 0x8A150007 | -1978335225 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION | Cannot process manifest. The manifest version is higher than supported. Please update the client. | -| 0x8A150008 | -1978335224 | APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED | Downloading installer failed | -| 0x8A150009 | -1978335223 | APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX | Cannot write to index; it is a higher schema version | -| 0x8A15000A | -1978335222 | APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED | The index is corrupt | -| 0x8A15000B | -1978335221 | APPINSTALLER_CLI_ERROR_SOURCES_INVALID | The configured source information is corrupt | -| 0x8A15000C | -1978335220 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS | The source name is already configured | -| 0x8A15000D | -1978335219 | APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE | The source type is invalid | -| 0x8A15000E | -1978335218 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE | The MSIX file is a bundle, not a package | -| 0x8A15000F | -1978335217 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING | Data required by the source is missing | -| 0x8A150010 | -1978335216 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER | None of the installers are applicable for the current system | -| 0x8A150011 | -1978335215 | APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH | The installer file's hash does not match the manifest | -| 0x8A150012 | -1978335214 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST | The source name does not exist | -| 0x8A150013 | -1978335213 | APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS | The source location is already configured under another name | -| 0x8A150014 | -1978335212 | APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND | No packages found | -| 0x8A150015 | -1978335211 | APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED | No sources are configured | -| 0x8A150016 | -1978335210 | APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND | Multiple packages found matching the criteria | -| 0x8A150017 | -1978335209 | APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND | No manifest found matching the criteria | -| 0x8A150018 | -1978335208 | APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED | Failed to get Public folder from source package | -| 0x8A150019 | -1978335207 | APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN | Command requires administrator privileges to run | -| 0x8A15001A | -1978335206 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE | The source location is not secure | -| 0x8A15001B | -1978335205 | APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY | The Microsoft Store client is blocked by policy | -| 0x8A15001C | -1978335204 | APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY | The Microsoft Store app is blocked by policy | -| 0x8A15001D | -1978335203 | APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED | The feature is currently under development. It can be enabled using winget settings. | -| 0x8A15001E | -1978335202 | APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED | Failed to install the Microsoft Store app | -| 0x8A15001F | -1978335201 | APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD | Failed to perform auto complete | -| 0x8A150020 | -1978335200 | APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED | Failed to initialize YAML parser | -| 0x8A150021 | -1978335199 | APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY | Encountered an invalid YAML key | -| 0x8A150022 | -1978335198 | APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY | Encountered a duplicate YAML key | -| 0x8A150023 | -1978335197 | APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION | Invalid YAML operation | -| 0x8A150024 | -1978335196 | APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED | Failed to build YAML doc | -| 0x8A150025 | -1978335195 | APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE | Invalid YAML emitter state | -| 0x8A150026 | -1978335194 | APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA | Invalid YAML data | -| 0x8A150027 | -1978335193 | APPINSTALLER_CLI_ERROR_LIBYAML_ERROR | LibYAML error | -| 0x8A150028 | -1978335192 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING | Manifest validation succeeded with warning | -| 0x8A150029 | -1978335191 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE | Manifest validation failed | -| 0x8A15002A | -1978335190 | APPINSTALLER_CLI_ERROR_INVALID_MANIFEST | Manifest is invalid | -| 0x8A15002B | -1978335189 | APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE | No applicable update found | -| 0x8A15002C | -1978335188 | APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE | winget upgrade --all completed with failures | -| 0x8A15002D | -1978335187 | APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED | Installer failed security check | -| 0x8A15002E | -1978335186 | APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH | Download size does not match expected content length | -| 0x8A15002F | -1978335185 | APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND | Uninstall command not found | -| 0x8A150030 | -1978335184 | APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED | Running uninstall command failed | -| 0x8A150031 | -1978335183 | APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR | ICU break iterator error | -| 0x8A150032 | -1978335182 | APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR | ICU casemap error | -| 0x8A150033 | -1978335181 | APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR | ICU regex error | -| 0x8A150034 | -1978335180 | APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED | Failed to install one or more imported packages | -| 0x8A150035 | -1978335179 | APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND | Could not find one or more requested packages | -| 0x8A150036 | -1978335178 | APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE | Json file is invalid | -| 0x8A150037 | -1978335177 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE | The source location is not remote | -| 0x8A150038 | -1978335176 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE | The configured rest source is not supported | -| 0x8A150039 | -1978335175 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA | Invalid data returned by rest source | -| 0x8A15003A | -1978335174 | APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY | Operation is blocked by Group Policy | -| 0x8A15003B | -1978335173 | APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR | Rest API internal error | -| 0x8A15003C | -1978335172 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL | Invalid rest source url | -| 0x8A15003D | -1978335171 | APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE | Unsupported MIME type returned by rest API | -| 0x8A15003E | -1978335170 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION | Invalid rest source contract version | -| 0x8A15003F | -1978335169 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE | The source data is corrupted or tampered | -| 0x8A150040 | -1978335168 | APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE | Error reading from the stream | -| 0x8A150041 | -1978335167 | APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED | Package agreements were not agreed to | -| 0x8A150042 | -1978335166 | APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR | Error reading input in prompt | -| 0x8A150043 | -1978335165 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST | The search request is not supported by one or more sources | -| 0x8A150044 | -1978335164 | APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND | The rest API endpoint is not found. | -| 0x8A150045 | -1978335163 | APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED | Failed to open the source. | -| 0x8A150046 | -1978335162 | APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED | Source agreements were not agreed to | -| 0x8A150047 | -1978335161 | APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH | Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. | -| 0x8A150048 | -1978335160 | APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE | Missing resource file | -| 0x8A150049 | -1978335159 | APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED | Running MSI install failed | -| 0x8A15004A | -1978335158 | APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT | Arguments for msiexec are invalid | -| 0x8A15004B | -1978335157 | APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES | Failed to open one or more sources | -| 0x8A15004C | -1978335156 | APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED | Failed to validate dependencies | -| 0x8A15004D | -1978335155 | APPINSTALLER_CLI_ERROR_MISSING_PACKAGE | One or more package is missing | -| 0x8A15004E | -1978335154 | APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN | Invalid table column | -| 0x8A15004F | -1978335153 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER | The upgrade version is not newer than the installed version | -| 0x8A150050 | -1978335152 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN | Upgrade version is unknown and override is not specified | -| 0x8A150051 | -1978335151 | APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR | ICU conversion error | -| 0x8A150052 | -1978335150 | APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED | Failed to install portable package | -| 0x8A150053 | -1978335149 | APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED | Volume does not support reparse points. | -| 0x8A150054 | -1978335148 | APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS | Portable package from a different source already exists. | -| 0x8A150055 | -1978335147 | APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY | Unable to create symlink, path points to a directory. | -| 0x8A150056 | -1978335146 | APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION | The installer cannot be run from an administrator context. | -| 0x8A150057 | -1978335145 | APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED | Failed to uninstall portable package | -| 0x8A150058 | -1978335144 | APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED | Failed to validate DisplayVersion values against index. | -| 0x8A150059 | -1978335143 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT | One or more arguments are not supported. | -| 0x8A15005A | -1978335142 | APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL | Embedded null characters are disallowed for SQLite | -| 0x8A15005B | -1978335141 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND | Failed to find the nested installer in the archive. | -| 0x8A15005C | -1978335140 | APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED | Failed to extract archive. | -| 0x8A15005D | -1978335139 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH | Invalid relative file path to nested installer provided. | -| 0x8A15005E | -1978335138 | APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH | The server certificate did not match any of the expected values. | -| 0x8A15005F | -1978335137 | APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED | Install location must be provided. | -| 0x8A150060 | -1978335136 | APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED | Archive malware scan failed. | -| 0x8A150061 | -1978335135 | APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED | Found at least one version of the package installed. | -| 0x8A150062 | -1978335134 | APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS | A pin already exists for the package. | -| 0x8A150063 | -1978335133 | APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST | There is no pin for the package. | -| 0x8A150064 | -1978335132 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX | Unable to open the pin database. | -| 0x8A150065 | -1978335131 | APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED | One or more applications failed to install | -| 0x8A150066 | -1978335130 | APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED | One or more applications failed to uninstall | -| 0x8A150067 | -1978335129 | APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE | One or more queries did not return exactly one match | -| 0x8A150068 | -1978335128 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED | The package has a pin that prevents upgrade. | -| 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | -| 0x8A15006A | -1978335126 | APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED | Application shutdown signal received | -| 0x8A15006B | -1978335125 | APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES | Failed to download package dependencies. | -| 0x8A15006C | -1978335124 | APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED | Failed to download package. Download for offline installation is prohibited. | -| 0x8A15006D | -1978335123 | APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE | A required service is busy or unavailable. Try again later. | -| 0x8A15006E | -1978335122 | APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND | The guid provided does not correspond to a valid resume state. | -| 0x8A15006F | -1978335121 | APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH | The current client version did not match the client version of the saved state. | -| 0x8A150070 | -1978335120 | APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE | The resume state data is invalid. | -| 0x8A150071 | -1978335119 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX | Unable to open the checkpoint database. | -| 0x8A150072 | -1978335118 | APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED | Exceeded max resume limit. | -| 0x8A150073 | -1978335117 | APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO | Invalid authentication info. | -| 0x8A150074 | -1978335116 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED | Authentication method not supported. | -| 0x8A150075 | -1978335115 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED | Authentication failed. | -| 0x8A150076 | -1978335114 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED | Authentication failed. Interactive authentication required. | -| 0x8A150077 | -1978335113 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER | Authentication failed. User cancelled. | -| 0x8A150078 | -1978335112 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT | Authentication failed. Authenticated account is not the desired account. | -| 0x8A150079 | -1978335111 | APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND | Repair command not found. | -| 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | -| 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | -| 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | -| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | -| 0x8A15007E | -1978335106 | APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED | The SQLite connection was terminated to prevent corruption. | -| 0x8A15007F | -1978335105 | APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED | Failed to get Microsoft Store package catalog. | -| 0x8A150080 | -1978335104 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE | No applicable Microsoft Store package found from Microsoft Store package catalog. | -| 0x8A150081 | -1978335103 | APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED | Failed to get Microsoft Store package download information. | -| 0x8A150082 | -1978335102 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE | No applicable Microsoft Store package download information found. | -| 0x8A150083 | -1978335101 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED | Failed to retrieve Microsoft Store package license. | -| 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download command. | -| 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. | -| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. | -| 0x8A150087 | -1979335097 | APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED | Failed installing one or more fonts. | -| 0x8A150088 | -1979335096 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED | Font file is not supported and cannot be installed. | -| 0x8A150089 | -1979335095 | APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED | Font package is already installed. | -| 0x8A15008A | -1979335094 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND | Font file not found. | -| 0x8A15008B | -1979335093 | APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED | Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. | -| 0x8A15008C | -1979335092 | APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED | Font validation failed. | -| 0x8A15008D | -1979335091 | APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED | Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. | -| 0x8A15008E | -1979335090 | APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH | An upgrade is available but uses a different install technology than the current installation | - -## Install errors. - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150101 | -1978334975 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE | Application is currently running. Exit the application then try again. | -| 0x8A150102 | -1978334974 | APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS | Another installation is already in progress. Try again later. | -| 0x8A150103 | -1978334973 | APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE | One or more file is being used. Exit the application then try again. | -| 0x8A150104 | -1978334972 | APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY | This package has a dependency missing from your system. | -| 0x8A150105 | -1978334971 | APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL | There's no more space on your PC. Make space, then try again. | -| 0x8A150106 | -1978334970 | APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY | There's not enough memory available to install. Close other applications then try again. | -| 0x8A150107 | -1978334969 | APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK | This application requires internet connectivity. Connect to a network then try again. | -| 0x8A150108 | -1978334968 | APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT | This application encountered an error during installation. Contact support. | -| 0x8A150109 | -1978334967 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH | Restart your PC to finish installation. | -| 0x8A15010A | -1978334966 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL | Installation failed. Restart your PC then try again. | -| 0x8A15010B | -1978334965 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED | Your PC will restart to finish installation. | -| 0x8A15010C | -1978334964 | APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER | You cancelled the installation. | -| 0x8A15010D | -1978334963 | APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED | Another version of this application is already installed. | -| 0x8A15010E | -1978334962 | APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE | A higher version of this application is already installed. | -| 0x8A15010F | -1978334961 | APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY | Organization policies are preventing installation. Contact your admin. | -| 0x8A150110 | -1978334960 | APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES | Failed to install package dependencies. | -| 0x8A150111 | -1978334959 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION | Application is currently in use by another application. | -| 0x8A150112 | -1978334958 | APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER | Invalid parameter. | -| 0x8A150113 | -1978334957 | APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED | Package not supported by the system. | -| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. | -| 0x8A150115 | -1978334955 | APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR | Installation failed with installer custom error. | - -## Check for package installed status - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150201 | -1978334719 | WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND | The Apps and Features Entry for the package could not be found. | -| 0x8A150202 | -1978334718 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE | The install location is not applicable. | -| 0x8A150203 | -1978334717 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND | The install location could not be found. | -| 0x8A150204 | -1978334716 | WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH | The hash of the existing file did not match. | -| 0x8A150205 | -1978334715 | WINGET_INSTALLED_STATUS_FILE_NOT_FOUND | File not found. | -| 0x8A150206 | -1978334714 | WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK | The file was found but the hash was not checked. | -| 0x8A150207 | -1978334713 | WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR | The file could not be accessed. | - -## Configuration Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A15C001 | -1978286079 | WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE | The configuration file is invalid. | -| 0x8A15C002 | -1978286078 | WINGET_CONFIG_ERROR_INVALID_YAML | The YAML syntax is invalid. | -| 0x8A15C003 | -1978286077 | WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE | A configuration field has an invalid type. | -| 0x8A15C004 | -1978286076 | WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION | The configuration has an unknown version. | -| 0x8A15C005 | -1978286075 | WINGET_CONFIG_ERROR_SET_APPLY_FAILED | An error occurred while applying the configuration. | -| 0x8A15C006 | -1978286074 | WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER | The configuration contains a duplicate identifier. | -| 0x8A15C007 | -1978286073 | WINGET_CONFIG_ERROR_MISSING_DEPENDENCY | The configuration is missing a dependency. | -| 0x8A15C008 | -1978286072 | WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED | The configuration has an unsatisfied dependency. | -| 0x8A15C009 | -1978286071 | WINGET_CONFIG_ERROR_ASSERTION_FAILED | An assertion for the configuration unit failed. | -| 0x8A15C00A | -1978286070 | WINGET_CONFIG_ERROR_MANUALLY_SKIPPED | The configuration was manually skipped. | -| 0x8A15C00B | -1978286069 | WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED | A warning was thrown and the user declined to continue execution. | -| 0x8A15C00C | -1978286068 | WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE | The dependency graph contains a cycle which cannot be resolved. | -| 0x8A15C00D | -1978286067 | WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE | The configuration has an invalid field value. | -| 0x8A15C00E | -1978286066 | WINGET_CONFIG_ERROR_MISSING_FIELD | The configuration is missing a field. | -| 0x8A15C00F | -1978286065 | WINGET_CONFIG_ERROR_TEST_FAILED | Some of the configuration units failed while testing their state. | -| 0x8A15C010 | -1978286064 | WINGET_CONFIG_ERROR_TEST_NOT_RUN | Configuration state was not tested. | -| 0x8A15C011 | -1978286063 | WINGET_CONFIG_ERROR_GET_FAILED | The configuration unit failed getting its properties. | -| 0x8A15C012 | -1978286062 | WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND | The specified configuration could not be found. | -| 0x8A15C013 | -1978286061 | WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY | Parameter cannot be passed across integrity boundary. | - -## Configuration Processor Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A15C101 | -1978285823 | WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED | The configuration unit was not installed. | -| 0x8A15C102 | -1978285822 | WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY | The configuration unit could not be found. | -| 0x8A15C103 | -1978285821 | WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES | Multiple matches were found for the configuration unit specify the module to select the correct one. | -| 0x8A15C104 | -1978285820 | WINGET_CONFIG_ERROR_UNIT_INVOKE_GET | The configuration unit failed while attempting to get the current system state. | -| 0x8A15C105 | -1978285819 | WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST | The configuration unit failed while attempting to test the current system state. | -| 0x8A15C106 | -1978285818 | WINGET_CONFIG_ERROR_UNIT_INVOKE_SET | The configuration unit failed while attempting to apply the desired state. | -| 0x8A15C107 | -1978285817 | WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT | The module for the configuration unit is available in multiple locations with the same version. | -| 0x8A15C108 | -1978285816 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE | Loading the module for the configuration unit failed. | -| 0x8A15C109 | -1978285815 | WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT | The configuration unit returned an unexpected result during execution. | -| 0x8A15C110 | -1978285814 | WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT | A unit contains a setting that requires the config root. | -| 0x8A15C111 | -1978285813 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN | Loading the module for the configuration unit failed because it requires administrator privileges to run. | -| 0x8A15C112 | -1978285812 | WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR | Operation is not supported by the configuration processor. | +--- +title: WinGet HRESULT Codes +description: WinGet return codes and their meanings +ms.date: 05/13/2026 +ms.topic: article +ms.localizationpriority: low +--- + +> [!NOTE] +> This file is generated by running: winget error --output + +# Return Codes + +## General Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150001 | -1978335231 | APPINSTALLER_CLI_ERROR_INTERNAL_ERROR | Internal Error | +| 0x8A150002 | -1978335230 | APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS | Invalid command line arguments | +| 0x8A150003 | -1978335229 | APPINSTALLER_CLI_ERROR_COMMAND_FAILED | Executing command failed | +| 0x8A150004 | -1978335228 | APPINSTALLER_CLI_ERROR_MANIFEST_FAILED | Opening manifest failed | +| 0x8A150005 | -1978335227 | APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED | Cancellation signal received | +| 0x8A150006 | -1978335226 | APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED | Running ShellExecute failed | +| 0x8A150007 | -1978335225 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION | Cannot process manifest. The manifest version is higher than supported. Please update the client. | +| 0x8A150008 | -1978335224 | APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED | Downloading installer failed | +| 0x8A150009 | -1978335223 | APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX | Cannot write to index; it is a higher schema version | +| 0x8A15000A | -1978335222 | APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED | The index is corrupt | +| 0x8A15000B | -1978335221 | APPINSTALLER_CLI_ERROR_SOURCES_INVALID | The configured source information is corrupt | +| 0x8A15000C | -1978335220 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS | The source name is already configured | +| 0x8A15000D | -1978335219 | APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE | The source type is invalid | +| 0x8A15000E | -1978335218 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE | The MSIX file is a bundle, not a package | +| 0x8A15000F | -1978335217 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING | Data required by the source is missing | +| 0x8A150010 | -1978335216 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER | None of the installers are applicable for the current system | +| 0x8A150011 | -1978335215 | APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH | The installer file's hash does not match the manifest | +| 0x8A150012 | -1978335214 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST | The source name does not exist | +| 0x8A150013 | -1978335213 | APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS | The source location is already configured under another name | +| 0x8A150014 | -1978335212 | APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND | No packages found | +| 0x8A150015 | -1978335211 | APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED | No sources are configured | +| 0x8A150016 | -1978335210 | APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND | Multiple packages found matching the criteria | +| 0x8A150017 | -1978335209 | APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND | No manifest found matching the criteria | +| 0x8A150018 | -1978335208 | APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED | Failed to get Public folder from source package | +| 0x8A150019 | -1978335207 | APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN | Command requires administrator privileges to run | +| 0x8A15001A | -1978335206 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE | The source location is not secure | +| 0x8A15001B | -1978335205 | APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY | The Microsoft Store client is blocked by policy | +| 0x8A15001C | -1978335204 | APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY | The Microsoft Store app is blocked by policy | +| 0x8A15001D | -1978335203 | APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED | The feature is currently under development. It can be enabled using winget settings. | +| 0x8A15001E | -1978335202 | APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED | Failed to install the Microsoft Store app | +| 0x8A15001F | -1978335201 | APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD | Failed to perform auto complete | +| 0x8A150020 | -1978335200 | APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED | Failed to initialize YAML parser | +| 0x8A150021 | -1978335199 | APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY | Encountered an invalid YAML key | +| 0x8A150022 | -1978335198 | APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY | Encountered a duplicate YAML key | +| 0x8A150023 | -1978335197 | APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION | Invalid YAML operation | +| 0x8A150024 | -1978335196 | APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED | Failed to build YAML doc | +| 0x8A150025 | -1978335195 | APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE | Invalid YAML emitter state | +| 0x8A150026 | -1978335194 | APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA | Invalid YAML data | +| 0x8A150027 | -1978335193 | APPINSTALLER_CLI_ERROR_LIBYAML_ERROR | LibYAML error | +| 0x8A150028 | -1978335192 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING | Manifest validation succeeded with warning | +| 0x8A150029 | -1978335191 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE | Manifest validation failed | +| 0x8A15002A | -1978335190 | APPINSTALLER_CLI_ERROR_INVALID_MANIFEST | Manifest is invalid | +| 0x8A15002B | -1978335189 | APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE | No applicable update found | +| 0x8A15002C | -1978335188 | APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE | winget upgrade --all completed with failures | +| 0x8A15002D | -1978335187 | APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED | Installer failed security check | +| 0x8A15002E | -1978335186 | APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH | Download size does not match expected content length | +| 0x8A15002F | -1978335185 | APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND | Uninstall command not found | +| 0x8A150030 | -1978335184 | APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED | Running uninstall command failed | +| 0x8A150031 | -1978335183 | APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR | ICU break iterator error | +| 0x8A150032 | -1978335182 | APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR | ICU casemap error | +| 0x8A150033 | -1978335181 | APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR | ICU regex error | +| 0x8A150034 | -1978335180 | APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED | Failed to install one or more imported packages | +| 0x8A150035 | -1978335179 | APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND | Could not find one or more requested packages | +| 0x8A150036 | -1978335178 | APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE | Json file is invalid | +| 0x8A150037 | -1978335177 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE | The source location is not remote | +| 0x8A150038 | -1978335176 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE | The configured rest source is not supported | +| 0x8A150039 | -1978335175 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA | Invalid data returned by rest source | +| 0x8A15003A | -1978335174 | APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY | Operation is blocked by Group Policy | +| 0x8A15003B | -1978335173 | APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR | Rest API internal error | +| 0x8A15003C | -1978335172 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL | Invalid rest source url | +| 0x8A15003D | -1978335171 | APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE | Unsupported MIME type returned by rest API | +| 0x8A15003E | -1978335170 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION | Invalid rest source contract version | +| 0x8A15003F | -1978335169 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE | The source data is corrupted or tampered | +| 0x8A150040 | -1978335168 | APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE | Error reading from the stream | +| 0x8A150041 | -1978335167 | APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED | Package agreements were not agreed to | +| 0x8A150042 | -1978335166 | APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR | Error reading input in prompt | +| 0x8A150043 | -1978335165 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST | The search request is not supported by one or more sources | +| 0x8A150044 | -1978335164 | APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND | The rest API endpoint is not found. | +| 0x8A150045 | -1978335163 | APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED | Failed to open the source. | +| 0x8A150046 | -1978335162 | APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED | Source agreements were not agreed to | +| 0x8A150047 | -1978335161 | APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH | Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. | +| 0x8A150048 | -1978335160 | APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE | Missing resource file | +| 0x8A150049 | -1978335159 | APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED | Running MSI install failed | +| 0x8A15004A | -1978335158 | APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT | Arguments for msiexec are invalid | +| 0x8A15004B | -1978335157 | APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES | Failed to open one or more sources | +| 0x8A15004C | -1978335156 | APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED | Failed to validate dependencies | +| 0x8A15004D | -1978335155 | APPINSTALLER_CLI_ERROR_MISSING_PACKAGE | One or more package is missing | +| 0x8A15004E | -1978335154 | APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN | Invalid table column | +| 0x8A15004F | -1978335153 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER | The upgrade version is not newer than the installed version | +| 0x8A150050 | -1978335152 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN | Upgrade version is unknown and override is not specified | +| 0x8A150051 | -1978335151 | APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR | ICU conversion error | +| 0x8A150052 | -1978335150 | APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED | Failed to install portable package | +| 0x8A150053 | -1978335149 | APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED | Volume does not support reparse points | +| 0x8A150054 | -1978335148 | APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS | Portable package from a different source already exists. | +| 0x8A150055 | -1978335147 | APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY | Unable to create symlink, path points to a directory. | +| 0x8A150056 | -1978335146 | APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION | The installer cannot be run from an administrator context. | +| 0x8A150057 | -1978335145 | APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED | Failed to uninstall portable package | +| 0x8A150058 | -1978335144 | APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED | Failed to validate DisplayVersion values against index. | +| 0x8A150059 | -1978335143 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT | One or more arguments are not supported. | +| 0x8A15005A | -1978335142 | APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL | Embedded null characters are disallowed for SQLite | +| 0x8A15005B | -1978335141 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND | Failed to find the nested installer in the archive. | +| 0x8A15005C | -1978335140 | APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED | Failed to extract archive. | +| 0x8A15005D | -1978335139 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH | Invalid relative file path to nested installer provided. | +| 0x8A15005E | -1978335138 | APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH | The server certificate did not match any of the expected values. | +| 0x8A15005F | -1978335137 | APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED | Install location must be provided. | +| 0x8A150060 | -1978335136 | APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED | Archive malware scan failed. | +| 0x8A150061 | -1978335135 | APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED | Found at least one version of the package installed. | +| 0x8A150062 | -1978335134 | APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS | A pin already exists for the package. | +| 0x8A150063 | -1978335133 | APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST | There is no pin for the package. | +| 0x8A150064 | -1978335132 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX | Unable to open the pin database. | +| 0x8A150065 | -1978335131 | APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED | One or more applications failed to install | +| 0x8A150066 | -1978335130 | APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED | One or more applications failed to uninstall | +| 0x8A150067 | -1978335129 | APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE | One or more queries did not return exactly one match | +| 0x8A150068 | -1978335128 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED | The package has a pin that prevents upgrade. | +| 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | +| 0x8A15006A | -1978335126 | APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED | Application shutdown signal received | +| 0x8A15006B | -1978335125 | APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES | Failed to download package dependencies. | +| 0x8A15006C | -1978335124 | APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED | Failed to download package. Download for offline installation is prohibited. | +| 0x8A15006D | -1978335123 | APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE | A required service is busy or unavailable. Try again later. | +| 0x8A15006E | -1978335122 | APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND | The guid provided does not correspond to a valid resume state. | +| 0x8A15006F | -1978335121 | APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH | The current client version did not match the client version of the saved state. | +| 0x8A150070 | -1978335120 | APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE | The resume state data is invalid. | +| 0x8A150071 | -1978335119 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX | Unable to open the checkpoint database. | +| 0x8A150072 | -1978335118 | APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED | Exceeded max resume limit. | +| 0x8A150073 | -1978335117 | APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO | Invalid authentication info. | +| 0x8A150074 | -1978335116 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED | Authentication method not supported. | +| 0x8A150075 | -1978335115 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED | Authentication failed. | +| 0x8A150076 | -1978335114 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED | Authentication failed. Interactive authentication required. | +| 0x8A150077 | -1978335113 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER | Authentication failed. User cancelled. | +| 0x8A150078 | -1978335112 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT | Authentication failed. Authenticated account is not the desired account. | +| 0x8A150079 | -1978335111 | APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND | Repair command not found. | +| 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | +| 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | +| 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | +| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | +| 0x8A15007E | -1978335106 | APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED | The SQLite connection was terminated to prevent corruption. | +| 0x8A15007F | -1978335105 | APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED | Failed to get Microsoft Store package catalog. | +| 0x8A150080 | -1978335104 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE | No applicable Microsoft Store package found from Microsoft Store package catalog. | +| 0x8A150081 | -1978335103 | APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED | Failed to get Microsoft Store package download information. | +| 0x8A150082 | -1978335102 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE | No applicable Microsoft Store package download information found. | +| 0x8A150083 | -1978335101 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED | Failed to retrieve Microsoft Store package license. | +| 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download. | +| 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege. | +| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. | +| 0x8A150087 | -1978335097 | APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED | Failed installing one or more fonts. | +| 0x8A150088 | -1978335096 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED | Font file is not supported and cannot be installed. | +| 0x8A150089 | -1978335095 | APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED | Font package is already installed. | +| 0x8A15008A | -1978335094 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND | Font file not found. | +| 0x8A15008B | -1978335093 | APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED | Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. | +| 0x8A15008C | -1978335092 | APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED | Font validation failed. | +| 0x8A15008D | -1978335091 | APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED | Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. | +| 0x8A15008E | -1978335090 | APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH | An upgrade is available but uses a different install technology than the current installation | + +## Install errors. + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150101 | -1978334975 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE | Application is currently running. Exit the application then try again. | +| 0x8A150102 | -1978334974 | APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS | Another installation is already in progress. Try again later. | +| 0x8A150103 | -1978334973 | APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE | One or more file is being used. Exit the application then try again. | +| 0x8A150104 | -1978334972 | APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY | This package has a dependency missing from your system. | +| 0x8A150105 | -1978334971 | APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL | There's no more space on your PC. Make space, then try again. | +| 0x8A150106 | -1978334970 | APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY | There's not enough memory available to install. Close other applications then try again. | +| 0x8A150107 | -1978334969 | APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK | This application requires internet connectivity. Connect to a network then try again. | +| 0x8A150108 | -1978334968 | APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT | This application encountered an error during installation. Contact support. | +| 0x8A150109 | -1978334967 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH | Restart your PC to finish installation. | +| 0x8A15010A | -1978334966 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL | Installation failed. Restart your PC then try again. | +| 0x8A15010B | -1978334965 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED | Your PC will restart to finish installation. | +| 0x8A15010C | -1978334964 | APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER | You cancelled the installation. | +| 0x8A15010D | -1978334963 | APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED | Another version of this application is already installed. | +| 0x8A15010E | -1978334962 | APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE | A higher version of this application is already installed. | +| 0x8A15010F | -1978334961 | APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY | Organization policies are preventing installation. Contact your admin. | +| 0x8A150110 | -1978334960 | APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES | Failed to install package dependencies. | +| 0x8A150111 | -1978334959 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION | Application is currently in use by another application. | +| 0x8A150112 | -1978334958 | APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER | Invalid parameter. | +| 0x8A150113 | -1978334957 | APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED | Package not supported by the system. | +| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. | +| 0x8A150115 | -1978334955 | APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR | Installation failed with a custom installer error. | + +## Check for package installed status + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150201 | -1978334719 | WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND | The Apps and Features Entry for the package could not be found. | +| 0x0A150202 | 169148930 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE | The install location is not applicable. | +| 0x8A150203 | -1978334717 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND | The install location could not be found. | +| 0x8A150204 | -1978334716 | WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH | The hash of the existing file did not match. | +| 0x8A150205 | -1978334715 | WINGET_INSTALLED_STATUS_FILE_NOT_FOUND | File not found. | +| 0x0A150206 | 169148934 | WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK | The file was found but the hash was not checked. | +| 0x8A150207 | -1978334713 | WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR | The file could not be accessed. | + +## Configuration Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A15C001 | -1978286079 | WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE | The configuration file is invalid. | +| 0x8A15C002 | -1978286078 | WINGET_CONFIG_ERROR_INVALID_YAML | The YAML syntax is invalid. | +| 0x8A15C003 | -1978286077 | WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE | A configuration field has an invalid type. | +| 0x8A15C004 | -1978286076 | WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION | The configuration has an unknown version. | +| 0x8A15C005 | -1978286075 | WINGET_CONFIG_ERROR_SET_APPLY_FAILED | An error occurred while applying the configuration. | +| 0x8A15C006 | -1978286074 | WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER | The configuration contains a duplicate identifier. | +| 0x8A15C007 | -1978286073 | WINGET_CONFIG_ERROR_MISSING_DEPENDENCY | The configuration is missing a dependency. | +| 0x8A15C008 | -1978286072 | WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED | The configuration has an unsatisfied dependency. | +| 0x8A15C009 | -1978286071 | WINGET_CONFIG_ERROR_ASSERTION_FAILED | An assertion for the configuration unit failed. | +| 0x8A15C00A | -1978286070 | WINGET_CONFIG_ERROR_MANUALLY_SKIPPED | The configuration was manually skipped. | +| 0x8A15C00B | -1978286069 | WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED | The user declined to continue execution. | +| 0x8A15C00C | -1978286068 | WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE | The dependency graph contains a cycle which cannot be resolved. | +| 0x8A15C00D | -1978286067 | WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE | The configuration has an invalid field value. | +| 0x8A15C00E | -1978286066 | WINGET_CONFIG_ERROR_MISSING_FIELD | The configuration is missing a field. | +| 0x8A15C00F | -1978286065 | WINGET_CONFIG_ERROR_TEST_FAILED | Some of the configuration units failed while testing their state. | +| 0x8A15C010 | -1978286064 | WINGET_CONFIG_ERROR_TEST_NOT_RUN | Configuration state was not tested. | +| 0x8A15C011 | -1978286063 | WINGET_CONFIG_ERROR_GET_FAILED | The configuration unit failed getting its properties. | +| 0x8A15C012 | -1978286062 | WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND | The specified configuration could not be found. | +| 0x8A15C013 | -1978286061 | WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY | Parameter cannot be passed across integrity boundary. | + +## Configuration Processor Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A15C101 | -1978285823 | WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED | The configuration unit was not installed. | +| 0x8A15C102 | -1978285822 | WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY | The configuration unit could not be found. | +| 0x8A15C103 | -1978285821 | WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES | Multiple matches were found for the configuration unit; specify the module to select the correct one. | +| 0x8A15C104 | -1978285820 | WINGET_CONFIG_ERROR_UNIT_INVOKE_GET | The configuration unit failed while attempting to get the current system state. | +| 0x8A15C105 | -1978285819 | WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST | The configuration unit failed while attempting to test the current system state. | +| 0x8A15C106 | -1978285818 | WINGET_CONFIG_ERROR_UNIT_INVOKE_SET | The configuration unit failed while attempting to apply the desired state. | +| 0x8A15C107 | -1978285817 | WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT | The module for the configuration unit is available in multiple locations with the same version. | +| 0x8A15C108 | -1978285816 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE | Loading the module for the configuration unit failed. | +| 0x8A15C109 | -1978285815 | WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT | The configuration unit returned an unexpected result during execution. | +| 0x8A15C110 | -1978285808 | WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT | A unit contains a setting that requires the config root. | +| 0x8A15C111 | -1978285807 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN | Loading the module for the configuration unit failed because it requires administrator privileges to run. | +| 0x8A15C112 | -1978285806 | WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR | Operation is not supported by the configuration processor. | +| 0x8A15C113 | -1978285805 | WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH | The DSC processor hash provided does not match hash of the target file. | diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index f2a67fd9e9..3a37b38505 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36915.13 d17.14 +VisualStudioVersion = 17.14.36915.13 MinimumVisualStudioVersion = 10.0.40219.1 Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerCLIPackage", "AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj", "{6AA3791A-0713-4548-A357-87A323E7AC3A}" ProjectSection(ProjectDependencies) = postProject @@ -40,7 +40,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53 Get-VcxprojNugetPackageVersions.ps1 = Get-VcxprojNugetPackageVersions.ps1 ..\README.md = ..\README.md ..\doc\ReleaseNotes.md = ..\doc\ReleaseNotes.md - ..\doc\Settings.md = ..\doc\Settings.md Update-VcxprojNugetPackageVersions.ps1 = Update-VcxprojNugetPackageVersions.ps1 ..\WinGetInProcCom.nuspec = ..\WinGetInProcCom.nuspec ..\WinGetUtil.nuspec = ..\WinGetUtil.nuspec @@ -242,6 +241,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "copilot", "copilot", "{B978 ..\.github\copilot-instructions.md = ..\.github\copilot-instructions.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{3FF6C881-2548-486E-8D70-7555A90030F5}" + ProjectSection(SolutionItems) = preProject + ..\doc\windows\package-manager\winget\returnCodes.md = ..\doc\windows\package-manager\winget\returnCodes.md + ..\doc\Settings.md = ..\doc\Settings.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -1114,6 +1119,7 @@ Global {40D7CA7F-EB86-4345-9641-AD27180C559D} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} {5421394F-5619-4E4B-8923-F3FB30D5EFAD} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} {B978E358-D2BE-4FA7-A21A-6661F3744DD7} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {3FF6C881-2548-486E-8D70-7555A90030F5} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.cpp b/src/AppInstallerCLICore/Commands/DebugCommand.cpp index 127697d52e..d650eb2bb7 100644 --- a/src/AppInstallerCLICore/Commands/DebugCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DebugCommand.cpp @@ -10,6 +10,7 @@ #include #include "AppInstallerDownloader.h" #include "Sixel.h" +#include using namespace AppInstaller::CLI::Execution; @@ -64,6 +65,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), }); @@ -370,6 +372,30 @@ namespace AppInstaller::CLI } } + std::vector GetSignerCommand::GetArguments() const + { + return { + Argument{ "file", 'f', Args::Type::Manifest, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, + }; + } + + Resource::LocString GetSignerCommand::ShortDescription() const + { + return Utility::LocIndString("Get signer information"sv); + } + + Resource::LocString GetSignerCommand::LongDescription() const + { + return Utility::LocIndString("Gets the signing information for a given path."sv); + } + + void GetSignerCommand::ExecuteInternal(Execution::Context& context) const + { + std::string subject = Certificates::GetAuthenticodeSubject(context.Args.GetArg(Args::Type::Manifest)); + + context.Reporter.Info() << "Subject: " << subject << std::endl; + } + // ── LogViewerTestCommand ───────────────────────────────────────────────────── #define WINGET_DEBUG_LOG_VIEWER_FOLLOW Args::Type::Force diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.h b/src/AppInstallerCLICore/Commands/DebugCommand.h index f1c38a3575..1c0fc2eecb 100644 --- a/src/AppInstallerCLICore/Commands/DebugCommand.h +++ b/src/AppInstallerCLICore/Commands/DebugCommand.h @@ -100,6 +100,20 @@ namespace AppInstaller::CLI void ExecuteInternal(Execution::Context& context) const override; }; + // Invokes signing information collection for a path. + struct GetSignerCommand final : public Command + { + GetSignerCommand(std::string_view parent) : Command("get-signer", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + // Tests the log viewer extension by emitting logs that exercise all channels, levels, subchannels, // continuation lines, long lines, and optionally a streaming follow mode. struct LogViewerTestCommand final : public Command diff --git a/src/AppInstallerCLICore/Commands/ErrorCommand.cpp b/src/AppInstallerCLICore/Commands/ErrorCommand.cpp index ebdc4e0491..387fd35937 100644 --- a/src/AppInstallerCLICore/Commands/ErrorCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ErrorCommand.cpp @@ -6,6 +6,8 @@ #include "AppInstallerErrors.h" #include "VTSupport.h" +#include + namespace AppInstaller::CLI { namespace @@ -23,12 +25,123 @@ namespace AppInstaller::CLI info << std::endl; info << error.GetDescription() << std::endl; } + + struct ErrorGroup + { + std::string_view Name; + WORD MinCode; // HRESULT_CODE, inclusive + WORD MaxCode; // HRESULT_CODE, inclusive + }; + + // Groups matching the sections in the published returnCodes.md documentation. + constexpr ErrorGroup s_errorGroups[] = + { + { "General Errors", 0x0001, 0x00FF }, + { "Install errors.", 0x0100, 0x01FF }, + { "Check for package installed status", 0x0200, 0x02FF }, + { "Configuration Errors", 0xC000, 0xC0FF }, + { "Configuration Processor Errors", 0xC100, 0xC1FF }, + }; + + constexpr std::string_view s_uncategorizedGroupName = "Uncategorized"; + + // Writes the markdown table for a group of errors to the given stream. + void WriteMarkdownGroup(std::ostream& out, std::string_view groupName, std::vector& errors) + { + if (errors.empty()) + { + return; + } + + std::sort(errors.begin(), errors.end(), + [](const Errors::HResultInformation* a, const Errors::HResultInformation* b) { return HRESULT_CODE(a->Value()) < HRESULT_CODE(b->Value()); }); + + out << "\n## " << groupName << "\n\n"; + out << "| Hex | Decimal | Symbol | Description |\n"; + out << "|-------------|-------------|-------------|-------------|\n"; + + for (const auto* error : errors) + { + HRESULT hr = error->Value(); + char hexBuf[11]; + sprintf_s(hexBuf, "0x%08X", static_cast(hr)); + + out << "| " << hexBuf + << " | " << static_cast(hr) + << " | " << error->Symbol() + << " | " << error->GetDescription() + << " |\n"; + } + } + + void OutputErrorMarkdown(Execution::Context& context) + { + auto allErrors = Errors::GetWinGetErrors(); + + // Categorize each error into its group; the last slot holds uncategorized errors. + static constexpr size_t s_errorGroupsCount = ARRAYSIZE(s_errorGroups); + std::vector> groupedErrors(s_errorGroupsCount + 1); + + for (const auto& error : allErrors) + { + HRESULT hr = error->Value(); + WORD code = static_cast(HRESULT_CODE(hr)); + + bool placed = false; + for (size_t i = 0; i < s_errorGroupsCount; ++i) + { + if (code >= s_errorGroups[i].MinCode && code <= s_errorGroups[i].MaxCode) + { + groupedErrors[i].push_back(error.get()); + placed = true; + break; + } + } + + if (!placed) + { + groupedErrors[s_errorGroupsCount].push_back(error.get()); + } + } + + std::filesystem::path outputPath{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; + std::ofstream out{ outputPath }; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED), !out); + + std::time_t now = std::time(nullptr); + std::tm tm{}; + localtime_s(&tm, &now); + char dateBuf[11]; + strftime(dateBuf, sizeof(dateBuf), "%m/%d/%Y", &tm); + + out << + "---\n" + "title: WinGet HRESULT Codes\n" + "description: WinGet return codes and their meanings\n" + "ms.date: " << dateBuf << "\n" + "ms.topic: article\n" + "ms.localizationpriority: low\n" + "---\n" + "\n" + "> [!NOTE]\n" + "> This file is generated by running: winget error --output \n" + "\n" + "# Return Codes\n"; + + for (size_t i = 0; i < ARRAYSIZE(s_errorGroups); ++i) + { + WriteMarkdownGroup(out, s_errorGroups[i].Name, groupedErrors[i]); + } + + WriteMarkdownGroup(out, s_uncategorizedGroupName, groupedErrors[s_errorGroupsCount]); + } } std::vector ErrorCommand::GetArguments() const { return { - Argument{ Execution::Args::Type::ErrorInput, Resource::String::ErrorInputArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::ErrorInput, Resource::String::ErrorInputArgumentDescription, ArgumentType::Positional }, + Argument{ Execution::Args::Type::OutputFile, Resource::String::ErrorOutputFileArgumentDescription, ArgumentType::Standard }, }; } @@ -42,8 +155,27 @@ namespace AppInstaller::CLI return { Resource::String::ErrorCommandLongDescription }; } + void ErrorCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::OutputFile) && execArgs.Contains(Execution::Args::Type::ErrorInput)) + { + throw CommandException(Resource::String::ErrorOutputFileConflictsWithInput); + } + + if (!execArgs.Contains(Execution::Args::Type::OutputFile) && !execArgs.Contains(Execution::Args::Type::ErrorInput)) + { + throw CommandException(Resource::String::ErrorRequiresInputOrOutputFile); + } + } + void ErrorCommand::ExecuteInternal(Execution::Context& context) const { + if (context.Args.Contains(Execution::Args::Type::OutputFile)) + { + OutputErrorMarkdown(context); + return; + } + std::string input{ context.Args.GetArg(Execution::Args::Type::ErrorInput) }; const char* begin = input.c_str(); diff --git a/src/AppInstallerCLICore/Commands/ErrorCommand.h b/src/AppInstallerCLICore/Commands/ErrorCommand.h index f67c887a84..af5336583e 100644 --- a/src/AppInstallerCLICore/Commands/ErrorCommand.h +++ b/src/AppInstallerCLICore/Commands/ErrorCommand.h @@ -16,6 +16,7 @@ namespace AppInstaller::CLI Resource::LocString LongDescription() const override; protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp index 4aade97ef7..d2e98dd2c9 100644 --- a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp +++ b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp @@ -341,10 +341,12 @@ namespace AppInstaller::CLI::ConfigurationRemoting { winrt::hstring dscExecutablePathPropertyName = ToHString(PropertyName::DscExecutablePath); std::optional dscExecutablePath = m_dynamicFactory->GetFactoryMapValue(dscExecutablePathPropertyName); + bool usingFoundPath = false; if (!dscExecutablePath) { dscExecutablePath = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePath)); + usingFoundPath = true; } if (dscExecutablePath->empty()) @@ -355,6 +357,37 @@ namespace AppInstaller::CLI::ConfigurationRemoting } json["processorPath"] = Utility::ConvertToUTF8(dscExecutablePath.value()); + + if (usingFoundPath) + { + // FoundDscExecutablePathHash/IsAlias are computed on the remote side alongside FoundDscExecutablePath. + winrt::hstring pathHash = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathHash)); + if (!pathHash.empty()) + { + json["processorPathHash"] = Utility::ConvertToUTF8(pathHash); + } + + winrt::hstring pathIsAlias = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathIsAlias)); + if (!pathIsAlias.empty()) + { + json["processorPathIsAlias"] = (pathIsAlias == L"true"); + } + } + else + { + // DscExecutablePathHash/IsAlias are set locally via the audit block. + auto pathHash = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathHash)); + if (pathHash) + { + json["processorPathHash"] = Utility::ConvertToUTF8(pathHash.value()); + } + + auto pathIsAlias = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathIsAlias)); + if (pathIsAlias) + { + json["processorPathIsAlias"] = (pathIsAlias.value() == L"true"); + } + } } Json::StreamWriterBuilder writerBuilder; diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp index 746743b2c4..cc7e570257 100644 --- a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp +++ b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -381,8 +381,11 @@ namespace AppInstaller::CLI::ConfigurationRemoting case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; case PropertyName::FindDscStateMachine: return L"FindDscStateMachine"; + case PropertyName::DscExecutablePathHash: return L"DscExecutablePathHash"; + case PropertyName::DscExecutablePathIsAlias: return L"DscExecutablePathIsAlias"; + case PropertyName::FoundDscExecutablePathHash: return L"FoundDscExecutablePathHash"; + case PropertyName::FoundDscExecutablePathIsAlias: return L"FoundDscExecutablePathIsAlias"; } - THROW_HR(E_UNEXPECTED); } } diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h index c690ed589e..ed3ec7a34a 100644 --- a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h +++ b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -44,6 +44,22 @@ namespace AppInstaller::CLI::ConfigurationRemoting // We must respond to the value it returns to properly transition states. // Read only. FindDscStateMachine, + // The SHA256 hash of the DscExecutablePath content: file bytes for regular executables, or + // raw reparse data buffer bytes for app execution aliases. + // Must be set alongside DscExecutablePath when a custom processor path is used. + // Read / Write + DscExecutablePathHash, + // Whether DscExecutablePath refers to an app execution alias + // (e.g., a path under %LOCALAPPDATA%\Microsoft\WindowsApps). + // Read / Write + DscExecutablePathIsAlias, + // The SHA256 hash of the FoundDscExecutablePath content. + // Computed alongside FoundDscExecutablePath; available after FoundDscExecutablePath is queried. + // Read only. + FoundDscExecutablePathHash, + // Whether FoundDscExecutablePath refers to an app execution alias. + // Read only. + FoundDscExecutablePathIsAlias, }; // Gets the string for a property name. diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index fa369b8ae5..a305f82b93 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -102,6 +102,13 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAudit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditHash); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditIsAlias); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditSignature); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditUnsigned); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathHashVerificationFailed); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); @@ -260,6 +267,9 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ErrorInputArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ErrorNumberIsTooLarge); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileConflictsWithInput); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorRequiresInputOrOutputFile); WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp index 99a15491f8..8d39ea5f1a 100644 --- a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include using namespace AppInstaller::CLI::Execution; using namespace winrt::Microsoft::Management::Configuration; @@ -142,6 +144,90 @@ namespace AppInstaller::CLI::Workflow return Logging::Level::Info; } + // Audit information gathered about a custom processor path. + struct ProcessorPathInfo + { + bool IsAlias = false; + std::string HashString; + std::string SigningSubject; + }; + + // Collects audit information for the given processor path. + // Throws on access failure so the caller is prevented from using an unverifiable path. + ProcessorPathInfo CollectProcessorPathInfo(const std::filesystem::path& processorPath) + { + ProcessorPathInfo result; + const std::wstring& pathStr = processorPath.wstring(); + + // Attempt to open the file for reading without FILE_FLAG_OPEN_REPARSE_POINT. + // App execution aliases (IO_REPARSE_TAG_APPEXECLINK) cannot be opened for reading + // this way and will fail with ERROR_CANT_ACCESS_FILE. + wil::unique_hfile fileHandle{ CreateFileW( + pathStr.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + if (!fileHandle) + { + DWORD lastError = GetLastError(); + THROW_WIN32_IF(lastError, lastError != ERROR_CANT_ACCESS_FILE); + + // Re-open with FILE_FLAG_OPEN_REPARSE_POINT to inspect the reparse data. + wil::unique_hfile reparseHandle{ CreateFileW( + pathStr.c_str(), + 0, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + nullptr) }; + THROW_LAST_ERROR_IF(!reparseHandle); + + // Retrieve the reparse point data. + std::vector reparseBuffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD bytesReturned = 0; + THROW_LAST_ERROR_IF(!DeviceIoControl( + reparseHandle.get(), + FSCTL_GET_REPARSE_POINT, + nullptr, + 0, + reparseBuffer.data(), + static_cast(reparseBuffer.size()), + &bytesReturned, + nullptr)); + + // Confirm it is specifically an app execution alias, not another reparse type. + THROW_HR_IF(E_INVALIDARG, bytesReturned < sizeof(DWORD)); + DWORD reparseTag = *reinterpret_cast(reparseBuffer.data()); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_REPARSE_TAG_MISMATCH), reparseTag != IO_REPARSE_TAG_APPEXECLINK); + + result.IsAlias = true; + result.HashString = Utility::SHA256::ConvertToString( + Utility::SHA256::ComputeHash(reparseBuffer.data(), bytesReturned)); + } + else + { + // Regular file: hash the file bytes using the shared SHA256 utility. + result.HashString = Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHashFromHandle(fileHandle.get())); + + // Attempt to extract signing info (handles both embedded and catalog signatures). + try + { + result.SigningSubject = Certificates::GetAuthenticodeSubject(processorPath); + } + catch (...) + { + AICLI_LOG(Config, Warning, << "Failed to retrieve signing info for processor path"); + } + } + + return result; + } + DiagnosticLevel ConvertLevel(Logging::Level level) { switch (level) @@ -241,7 +327,40 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Args::Type::ConfigurationProcessorPath)) { - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), Utility::ConvertToUTF16(context.Args.GetArg(Args::Type::ConfigurationProcessorPath))); + progressScope.reset(); + + const auto& processorPathArg = context.Args.GetArg(Args::Type::ConfigurationProcessorPath); + std::filesystem::path processorPath{ Utility::ConvertToUTF16(processorPathArg) }; + + // Collect audit information; throws if the path cannot be opened or hashed. + auto pathInfo = anon::CollectProcessorPathInfo(processorPath); + + // Output audit information to the user as a warning since this is a non-default path. + constexpr std::string_view s_indent = " "sv; + context.Reporter.Info() << Resource::String::ConfigurationProcessorPathAudit << std::endl; + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditPath(Utility::LocIndString{ processorPathArg }) << std::endl; + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditHash(Utility::LocIndString{ pathInfo.HashString }) << std::endl; + if (pathInfo.IsAlias) + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditIsAlias << std::endl; + } + else if (!pathInfo.SigningSubject.empty()) + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditSignature(Utility::LocIndString{ pathInfo.SigningSubject }) << std::endl; + } + else + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditUnsigned << std::endl; + } + + AICLI_LOG(Config, Info, << "Processor path audit - Path: " << processorPathArg << ", Hash: " << pathInfo.HashString << ", IsAlias: " << pathInfo.IsAlias); + + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), processorPath.wstring()); + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathHash), Utility::ConvertToUTF16(pathInfo.HashString)); + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathIsAlias), pathInfo.IsAlias ? L"true" : L"false"); + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); } else { diff --git a/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs new file mode 100644 index 0000000000..0b4fe54e77 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs @@ -0,0 +1,139 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.IO; + using System.Text.RegularExpressions; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// E2E tests for the `--processor-path` argument of the configure command. + /// These tests verify that audit information (path, hash, signing) is shown + /// when a custom DSC processor path is provided. + /// + public class ConfigureProcessorPathCommand + { + private const string Command = "configure"; + private const string ConfigurationProcessorPath = "ConfigurationProcessorPath"; + + // DSC app execution alias paths (stable then preview). + private static readonly string[] DscAliasCandidates = new[] + { + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe\dsc.exe"), + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe\dsc.exe"), + }; + + /// + /// Enables the LocalProcessorPath admin setting before each test. + /// + [SetUp] + public void SetUp() + { + WinGetSettingsHelper.EnableAdminSetting(ConfigurationProcessorPath); + } + + /// + /// Disables the LocalProcessorPath admin setting after each test. + /// + [TearDown] + public void TearDown() + { + WinGetSettingsHelper.DisableAdminSetting(ConfigurationProcessorPath); + } + + /// + /// Verifies that when the DSC executable is provided as the processor path, + /// the audit header, file path, SHA256 hash, and app-execution-alias marker + /// all appear in the output. + /// + [Test] + public void ProcessorPath_AuditOutput_ShowsPathAndHash() + { + string processorPath = FindDscExePath(); + if (processorPath == null) + { + Assert.Ignore("DSC is not installed; skipping processor path audit test."); + return; + } + + string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); + + var result = TestCommon.RunAICLICommand( + Command, + $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); + + // Audit header must appear regardless of whether the configure succeeds or fails, + // because audit output happens during factory setup before DSC is invoked. + Assert.True(result.StdOut.Contains("Custom processor path:"), $"Expected audit header in output. StdOut: {result.StdOut}"); + Assert.True(result.StdOut.Contains($" Path: {processorPath}"), $"Expected path in audit output. StdOut: {result.StdOut}"); + Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); + + // dsc.exe is an app execution alias; the alias marker must be present. + Assert.True(result.StdOut.Contains("Type: App execution alias"), $"Expected app execution alias marker. StdOut: {result.StdOut}"); + } + + /// + /// Verifies that the hash in the audit output is a 64-character lowercase hex string + /// (SHA256 of the file or reparse buffer contents). + /// + [Test] + public void ProcessorPath_AuditOutput_HashIsValidSHA256() + { + string processorPath = FindDscExePath(); + if (processorPath == null) + { + Assert.Ignore("DSC is not installed; skipping processor path hash format test."); + return; + } + + string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); + + var result = TestCommon.RunAICLICommand( + Command, + $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); + + Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); + + // Extract the hash value from " Hash: " + int hashLabelIndex = result.StdOut.IndexOf(" Hash: "); + Assert.That(hashLabelIndex, Is.GreaterThanOrEqualTo(0)); + + int hashStart = hashLabelIndex + " Hash: ".Length; + int hashEnd = result.StdOut.IndexOfAny(new[] { '\r', '\n' }, hashStart); + string hashValue = hashEnd > hashStart + ? result.StdOut.Substring(hashStart, hashEnd - hashStart).Trim() + : result.StdOut.Substring(hashStart).Trim(); + + Assert.AreEqual(64, hashValue.Length, $"Expected 64-character SHA256 hash, got: '{hashValue}'"); + Assert.True( + Regex.IsMatch(hashValue, "^[0-9a-f]{64}$"), + $"Expected lowercase hex hash, got: '{hashValue}'"); + } + + /// + /// Gets the path to dsc.exe (app execution alias), or null if not installed. + /// + private static string FindDscExePath() + { + foreach (string candidate in DscAliasCandidates) + { + if (File.Exists(candidate)) + { + return candidate; + } + } + + return null; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs index aa3420782e..95bf7f99b7 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs @@ -88,6 +88,24 @@ public static void InitializeWingetSettings() SetWingetSettings(JsonConvert.SerializeObject(settingsJson, Formatting.Indented)); } + /// + /// Enables an admin setting. + /// + /// The admin setting name. + public static void EnableAdminSetting(string settingName) + { + TestCommon.RunAICLICommand("settings", $"--enable {settingName}"); + } + + /// + /// Disables an admin setting. + /// + /// The admin setting name. + public static void DisableAdminSetting(string settingName) + { + TestCommon.RunAICLICommand("settings", $"--disable {settingName}"); + } + /// /// Configure experimental features. /// diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 177d2828c0..8754372ac8 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2156,6 +2156,18 @@ Please specify one of them using the --source option to proceed. A value to search within the error information + + Generate error code Markdown file + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + The --output argument cannot be combined with an input value. + {Locked="--output"} + + + An input value or --output must be provided. + {Locked="--output"} + The given number is too large to be an HRESULT. @@ -3279,6 +3291,34 @@ Please specify one of them using the --source option to proceed. Specify the path to the configuration processor + + Custom processor path: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Type: App execution alias + Indicates the processor path is an app execution alias (MSIX package link). + + + Path: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Signed By: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Signed By: (unsigned) + Indicates the custom processor executable does not have an Authenticode signature. + + + The integrity check failed for the custom DSC processor path. The path may have been modified. + Error when the server-side hash of the processor path does not match the client-computed hash. + DSC v3 resource commands DSC stands for "Desired State Configuration". It should already have a locked translation. @@ -3606,4 +3646,7 @@ An unlocalized JSON fragment will follow on another line. Results have been filtered to the highest matched source priority. A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. - + + The DSC processor hash provided does not match hash of the target file. + + \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Certificates.cpp b/src/AppInstallerSharedLib/Certificates.cpp index 96bff881b1..8ba7a77a58 100644 --- a/src/AppInstallerSharedLib/Certificates.cpp +++ b/src/AppInstallerSharedLib/Certificates.cpp @@ -12,6 +12,59 @@ namespace AppInstaller::Certificates { namespace { + // WTHelperProvDataFromStateData and WTHelperGetProvSignerFromChain are not in the wintrust import lib; + // resolve them at runtime via GetProcAddress. + using WTHelperProvDataFromStateDataPtr = decltype(&WTHelperProvDataFromStateData); + using WTHelperGetProvSignerFromChainPtr = decltype(&WTHelperGetProvSignerFromChain); + + struct WinTrustHelpers + { + WinTrustHelpers() + { + m_module.reset(LoadLibraryExW(L"wintrust.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (!m_module) + { + AICLI_LOG(Core, Warning, << "Could not load wintrust.dll"); + return; + } + + m_provDataFromStateData = reinterpret_cast( + GetProcAddress(m_module.get(), "WTHelperProvDataFromStateData")); + if (!m_provDataFromStateData) + { + AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperProvDataFromStateData"); + } + + m_provSignerFromChain = reinterpret_cast( + GetProcAddress(m_module.get(), "WTHelperGetProvSignerFromChain")); + if (!m_provSignerFromChain) + { + AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperGetProvSignerFromChain"); + } + } + + CRYPT_PROVIDER_DATA* ProvDataFromStateData(HANDLE stateData) const + { + return m_provDataFromStateData ? m_provDataFromStateData(stateData) : nullptr; + } + + CRYPT_PROVIDER_SGNR* ProvSignerFromChain(CRYPT_PROVIDER_DATA* provData, DWORD signerIdx, BOOL counterSigner, DWORD counterSignerIdx) const + { + return m_provSignerFromChain ? m_provSignerFromChain(provData, signerIdx, counterSigner, counterSignerIdx) : nullptr; + } + + private: + wil::unique_hmodule m_module; + WTHelperProvDataFromStateDataPtr m_provDataFromStateData = nullptr; + WTHelperGetProvSignerFromChainPtr m_provSignerFromChain = nullptr; + }; + + const WinTrustHelpers& GetWinTrustHelpers() + { + static WinTrustHelpers s_helpers; + return s_helpers; + } + std::string GetNameString(PCCERT_CONTEXT certContext, DWORD nameType, bool forIssuer, void* typeParam = nullptr) { if (!certContext) @@ -736,4 +789,57 @@ namespace AppInstaller::Certificates return result; } + + std::string GetAuthenticodeSubject(const std::filesystem::path& filePath) + { + const std::wstring& pathStr = filePath.wstring(); + + WINTRUST_FILE_INFO fileInfo = {}; + fileInfo.cbStruct = sizeof(fileInfo); + fileInfo.pcwszFilePath = pathStr.c_str(); + + WINTRUST_DATA trustData = {}; + trustData.cbStruct = sizeof(trustData); + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + trustData.pFile = &fileInfo; + + GUID actionId = WINTRUST_ACTION_GENERIC_VERIFY_V2; + LONG trustResult = WinVerifyTrust(reinterpret_cast(INVALID_HANDLE_VALUE), &actionId, &trustData); + + auto cleanup = wil::scope_exit([&]() + { + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(reinterpret_cast(INVALID_HANDLE_VALUE), &actionId, &trustData); + }); + + if (trustResult != 0) + { + return {}; + } + + CRYPT_PROVIDER_DATA* provData = GetWinTrustHelpers().ProvDataFromStateData(trustData.hWVTStateData); + if (!provData) + { + return {}; + } + + CRYPT_PROVIDER_SGNR* signer = GetWinTrustHelpers().ProvSignerFromChain(provData, 0, FALSE, 0); + if (!signer || signer->csCertChain == 0 || !signer->pasCertChain) + { + return {}; + } + + PCCERT_CONTEXT certContext = signer->pasCertChain[0].pCert; + if (!certContext) + { + return {}; + } + + DWORD strType = CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG; + return GetNameString(certContext, CERT_NAME_RDN_TYPE, false, &strType); + } } diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index fd83d8fece..0e9b811e87 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -296,6 +296,7 @@ namespace AppInstaller WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT, "A unit contains a setting that requires the config root."), WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN, "Loading the module for the configuration unit failed because it requires administrator privileges to run."), WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, "Operation is not supported by the configuration processor."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, "The DSC processor hash provided does not match hash of the target file."), // Errors without the error bit set WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE, "The install location is not applicable."), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index a8bfdf40da..6510e363ad 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -234,6 +234,7 @@ #define WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT ((HRESULT)0x8A15C110) #define WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN ((HRESULT)0x8A15C111) #define WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR ((HRESULT)0x8A15C112) +#define WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH ((HRESULT)0x8A15C113) namespace AppInstaller { diff --git a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h index 7e24ce075a..2272683b8b 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h @@ -68,6 +68,10 @@ namespace AppInstaller::Utility { // Computes the hash from a given file path. static HashBuffer ComputeHashFromFile(const std::filesystem::path& path); + // Computes the hash from an open file HANDLE by reading sequentially from the current position. + // The caller retains ownership of the handle. + static HashBuffer ComputeHashFromHandle(HANDLE fileHandle); + static std::string ConvertToString(const HashBuffer& hashBuffer); static std::wstring ConvertToWideString(const HashBuffer& hashBuffer); diff --git a/src/AppInstallerSharedLib/Public/winget/Certificates.h b/src/AppInstallerSharedLib/Public/winget/Certificates.h index e455b2c7d5..fb7f973bcc 100644 --- a/src/AppInstallerSharedLib/Public/winget/Certificates.h +++ b/src/AppInstallerSharedLib/Public/winget/Certificates.h @@ -6,13 +6,21 @@ #include #include +#include #include #include +#include #include namespace AppInstaller::Certificates { + // Returns the Authenticode signing subject name (e.g., "Microsoft Corporation") for the + // given file. Only returns a value when WinVerifyTrust confirms the signature is valid and + // trusted; extracts the subject from the embedded PE Authenticode signature via crypt32. + // Returns an empty string if the file is unsigned, untrusted, or on any error. + std::string GetAuthenticodeSubject(const std::filesystem::path& filePath); + // Defines the types of certificate pinning to perform. enum class PinningVerificationType : uint32_t { diff --git a/src/AppInstallerSharedLib/SHA256.cpp b/src/AppInstallerSharedLib/SHA256.cpp index e46508ea3c..ce71e0f77b 100644 --- a/src/AppInstallerSharedLib/SHA256.cpp +++ b/src/AppInstallerSharedLib/SHA256.cpp @@ -175,6 +175,21 @@ namespace AppInstaller::Utility { return targetFileHash; } + SHA256::HashBuffer SHA256::ComputeHashFromHandle(HANDLE fileHandle) + { + constexpr DWORD bufferSize = 1024 * 1024; + auto buffer = std::make_unique(bufferSize); + SHA256 hasher; + DWORD bytesRead = 0; + + while (ReadFile(fileHandle, buffer.get(), bufferSize, &bytesRead, nullptr) && bytesRead > 0) + { + hasher.Add(buffer.get(), bytesRead); + } + + return hasher.Get(); + } + void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) { delete context; diff --git a/src/AppInstallerSharedLib/pch.h b/src/AppInstallerSharedLib/pch.h index 22186ce6ba..2a7366d8aa 100644 --- a/src/AppInstallerSharedLib/pch.h +++ b/src/AppInstallerSharedLib/pch.h @@ -10,7 +10,9 @@ #include #include #include -#include +#include +#include +#include #define YAML_DECLARE_STATIC #include diff --git a/src/ConfigurationRemotingServer/Program.cs b/src/ConfigurationRemotingServer/Program.cs index 68d7170f87..19c65c8e49 100644 --- a/src/ConfigurationRemotingServer/Program.cs +++ b/src/ConfigurationRemotingServer/Program.cs @@ -178,6 +178,12 @@ private class LimitationSetMetadata [JsonPropertyName("processorPath")] public string? ProcessorPath { get; set; } = null; + + [JsonPropertyName("processorPathHash")] + public string? ProcessorPathHash { get; set; } = null; + + [JsonPropertyName("processorPathIsAlias")] + public bool? ProcessorPathIsAlias { get; set; } = null; } private static IConfigurationSetProcessorFactory CreateFactory(string processorEngine, ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) @@ -242,6 +248,14 @@ private static IConfigurationSetProcessorFactory CreateDSCv3Factory(Configuratio { if (limitationSetMetadata.ProcessorPath != null) { + if (limitationSetMetadata.ProcessorPathHash == null) + { + throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); + } + + // Set hash and alias before path so they are available when the path is verified on first use. + factory.DscExecutablePathHash = limitationSetMetadata.ProcessorPathHash; + factory.DscExecutablePathIsAlias = limitationSetMetadata.ProcessorPathIsAlias ?? false; factory.DscExecutablePath = limitationSetMetadata.ProcessorPath; } else diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 new file mode 100644 index 0000000000..12f8ec642f --- /dev/null +++ b/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$BuildOutputPath, + + [string]$PackageLayoutPath, + + [string]$TestCaseFilter, + + [string]$Platform = "x64", + + [string]$Configuration = "Debug" +) + +# Derive BuildOutputPath from the repo root if not specified +if ([System.String]::IsNullOrEmpty($BuildOutputPath)) +{ + $Local:repoRoot = git -C $PSScriptRoot rev-parse --show-toplevel + $BuildOutputPath = Join-Path $Local:repoRoot "src" $Platform $Configuration +} + +# Step 1: Prepare the test output directory +$Local:prepareScript = Join-Path $PSScriptRoot "Prepare-ConfigurationOOPTests.ps1" +& $Local:prepareScript -BuildOutputPath $BuildOutputPath -PackageLayoutPath $PackageLayoutPath + +if (-not $?) +{ + Write-Error "Preparation script failed" + exit 1 +} + +# Step 2: Find vstest.console.exe via vswhere +$Local:vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +if (-not (Test-Path $Local:vswhere)) +{ + Write-Error "vswhere.exe not found at '$Local:vswhere'. Is Visual Studio installed?" + exit 1 +} + +$Local:vsInstallPath = & $Local:vswhere -latest -products * -requires Microsoft.VisualStudio.PackageGroup.TestTools.Core -property installationPath +if ([System.String]::IsNullOrEmpty($Local:vsInstallPath)) +{ + Write-Error "Could not find a Visual Studio installation with test tools via vswhere." + exit 1 +} + +$Local:vstestPath = Join-Path $Local:vsInstallPath "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" +if (-not (Test-Path $Local:vstestPath)) +{ + Write-Error "vstest.console.exe not found at '$Local:vstestPath'." + exit 1 +} + +# Step 3: Run the tests with vstest +$Local:testDll = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.UnitTests.dll" + +$Local:vstestArgs = @( + $Local:testDll, + "--logger:console;verbosity=detailed" +) + +if (-not [System.String]::IsNullOrEmpty($TestCaseFilter)) +{ + $Local:vstestArgs += "--TestCaseFilter:$TestCaseFilter" +} + +& $Local:vstestPath @Local:vstestArgs +exit $LASTEXITCODE diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs new file mode 100644 index 0000000000..a0411a394b --- /dev/null +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs @@ -0,0 +1,202 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.Runtime.InteropServices; + using System.Security.Cryptography; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Win32.SafeHandles; + + /// + /// Provides integrity verification for the DSC processor executable path. + /// Handles both regular files and app execution alias reparse points. + /// + internal static class ProcessorPathIntegrity + { + private const uint GenericRead = 0x80000000; + private const uint FileShareRead = 0x00000001; + private const uint FileShareExecute = 0x00000004; + private const uint OpenExisting = 3; + private const uint FileAttributeNormal = 0x80; + private const uint FileFlagOpenReparsePoint = 0x00200000; + private const uint FileFlagBackupSemantics = 0x02000000; + private const uint FsctlGetReparsePoint = 0x000900A8; + private const int MaximumReparseDataBufferSize = 16 * 1024; + + /// + /// Opens the processor path, verifies its hash matches the expected value, and returns + /// an open handle for TOCTOU protection. + /// + /// The path to the DSC executable or app execution alias. + /// The expected SHA256 hash (hex string, case-insensitive). + /// Whether the path is an app execution alias reparse point. + /// An open SafeFileHandle to the file; caller should hold this for the process lifetime. + public static SafeFileHandle VerifyAndOpen(string path, string expectedHash, bool isAlias) + { + SafeFileHandle handle; + byte[] hashBytes; + + if (isAlias) + { + handle = CreateFile( + path, + 0, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileFlagOpenReparsePoint | FileFlagBackupSemantics, + IntPtr.Zero); + + if (handle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open processor path alias '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; + if (!DeviceIoControl(handle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) + { + handle.Dispose(); + throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + hashBytes = SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned)); + } + else + { + handle = CreateFile( + path, + GenericRead, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileAttributeNormal, + IntPtr.Zero); + + if (handle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open processor path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + hashBytes = ComputeSHA256FromHandle(handle); + } + + string computedHash = Convert.ToHexString(hashBytes).ToLowerInvariant(); + if (!string.Equals(computedHash, expectedHash, StringComparison.OrdinalIgnoreCase)) + { + handle.Dispose(); + throw new DscProcessorHashMismatchException(); + } + + return handle; + } + + /// + /// Computes the SHA256 hash of a path, auto-detecting whether it is an app execution alias. + /// + /// The path to hash. + /// Receives true if the path is an app execution alias reparse point. + /// The SHA256 hash as a lowercase hex string. + public static string ComputeHash(string path, out bool isAlias) + { + // Attempt to open as a regular file first. + SafeFileHandle regularHandle = CreateFile( + path, + GenericRead, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileAttributeNormal, + IntPtr.Zero); + + if (!regularHandle.IsInvalid) + { + isAlias = false; + using (regularHandle) + { + return Convert.ToHexString(ComputeSHA256FromHandle(regularHandle)).ToLowerInvariant(); + } + } + + // If the regular open fails, try as an app execution alias reparse point. + SafeFileHandle aliasHandle = CreateFile( + path, + 0, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileFlagOpenReparsePoint | FileFlagBackupSemantics, + IntPtr.Zero); + + if (aliasHandle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + using (aliasHandle) + { + byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; + if (!DeviceIoControl(aliasHandle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) + { + throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + isAlias = true; + return Convert.ToHexString(SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned))).ToLowerInvariant(); + } + } + + private static byte[] ComputeSHA256FromHandle(SafeFileHandle handle) + { + using var incrHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + + byte[] buffer = new byte[1024 * 1024]; + while (true) + { + if (!ReadFile(handle, buffer, (uint)buffer.Length, out uint bytesRead, IntPtr.Zero) || bytesRead == 0) + { + break; + } + + incrHash.AppendData(buffer, 0, (int)bytesRead); + } + + return incrHash.GetHashAndReset(); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeviceIoControl( + SafeFileHandle hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + byte[] lpOutBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ReadFile( + SafeFileHandle hFile, + byte[] lpBuffer, + uint nNumberOfBytesToRead, + out uint lpNumberOfBytesRead, + IntPtr lpOverlapped); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs index 04abcf394c..79f4cff589 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -12,18 +12,25 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers using System.Text; using Microsoft.Management.Configuration.Processor.DSCv3.Model; using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Win32.SafeHandles; /// /// Contains settings for the DSC v3 processor components to share. /// - internal class ProcessorSettings + internal class ProcessorSettings : IDisposable { private readonly object dscV3Lock = new (); private readonly object defaultPathLock = new (); + private readonly object processorPathLock = new (); private FindDscPackageStateMachine dscPackageStateMachine = new (); private IDSCv3? dscV3 = null; private string? defaultPath = null; + private string? defaultPathHash = null; + private bool? defaultPathIsAlias = null; + private SafeFileHandle? processorPathHandle = null; + private bool processorPathVerified = false; + private bool disposed = false; private Dictionary resourceDetailsDictionary = new (); @@ -32,6 +39,17 @@ internal class ProcessorSettings /// public string? DscExecutablePath { get; set; } + /// + /// Gets or sets the expected SHA256 hash of the DSC v3 executable (hex string). + /// Must be set before is accessed when a custom path is used. + /// + public string? DscExecutablePathHash { get; set; } + + /// + /// Gets or sets a value indicating whether is an app execution alias. + /// + public bool? DscExecutablePathIsAlias { get; set; } + /// /// Gets the path to the DSC v3 executable. /// @@ -41,6 +59,7 @@ public string EffectiveDscExecutablePath { if (this.DscExecutablePath != null) { + this.EnsureProcessorPathVerified(); return this.DscExecutablePath; } @@ -117,7 +136,39 @@ public IDSCv3 DSCv3 /// The full path to the dsc.exe executable, or null if not found. public string? GetFoundDscExecutablePath() { - return this.dscPackageStateMachine.DscExecutablePath; + string? result = this.dscPackageStateMachine.DscExecutablePath; + + if (result != null) + { + // Ensure hash and alias are computed and cached alongside the path. + this.EnsureFoundPathHashCached(result); + } + + return result; + } + + /// + /// Gets the SHA256 hash of the auto-discovered DSC executable path, or null if not yet discovered. + /// + /// Lowercase hex hash string, or null. + public string? GetFoundDscExecutablePathHash() + { + lock (this.defaultPathLock) + { + return this.defaultPathHash; + } + } + + /// + /// Gets whether the auto-discovered DSC executable path is an app execution alias, or null if not yet discovered. + /// + /// True if alias, false if regular file, or null if not yet discovered. + public bool? GetFoundDscExecutablePathIsAlias() + { + lock (this.defaultPathLock) + { + return this.defaultPathIsAlias; + } } /// @@ -129,6 +180,13 @@ public FindDscPackageStateMachine.Transition PumpFindDscStateMachine() return this.dscPackageStateMachine.DetermineNextTransition(); } + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + /// /// Create a deep copy of this settings object. /// @@ -140,7 +198,16 @@ public ProcessorSettings Clone() result.resourceDetailsDictionary = this.resourceDetailsDictionary; result.DiagnosticsSink = this.DiagnosticsSink; result.DscExecutablePath = this.DscExecutablePath; + result.DscExecutablePathHash = this.DscExecutablePathHash; + result.DscExecutablePathIsAlias = this.DscExecutablePathIsAlias; result.DiagnosticTraceEnabled = this.DiagnosticTraceEnabled; + lock (this.defaultPathLock) + { + result.defaultPath = this.defaultPath; + result.defaultPathHash = this.defaultPathHash; + result.defaultPathIsAlias = this.defaultPathIsAlias; + } + #if !AICLI_DISABLE_TEST_HOOKS result.dscV3 = this.DSCv3; #endif @@ -251,5 +318,59 @@ public List FindAllResourceDetails(FindUnitProcessorsOptions fi return result; } + + /// + /// Releases resources held by this instance, including the open handle used for TOCTOU protection. + /// + /// True if called from Dispose(); false if called from a finalizer. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + lock (this.processorPathLock) + { + this.processorPathHandle?.Dispose(); + this.processorPathHandle = null; + } + } + + this.disposed = true; + } + } + + private void EnsureFoundPathHashCached(string path) + { + lock (this.defaultPathLock) + { + if (this.defaultPathHash == null) + { + this.defaultPathHash = ProcessorPathIntegrity.ComputeHash(path, out bool isAlias); + this.defaultPathIsAlias = isAlias; + } + } + } + + private void EnsureProcessorPathVerified() + { + lock (this.processorPathLock) + { + if (!this.processorPathVerified) + { + if (this.DscExecutablePathHash == null) + { + throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); + } + + bool isAlias = this.DscExecutablePathIsAlias ?? false; + this.processorPathHandle = ProcessorPathIntegrity.VerifyAndOpen( + this.DscExecutablePath!, + this.DscExecutablePathHash, + isAlias); + this.processorPathVerified = true; + } + } + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs new file mode 100644 index 0000000000..70790ff8ed --- /dev/null +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// DSC processor does not match provided hash. + /// + internal class DscProcessorHashMismatchException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public DscProcessorHashMismatchException() + : base("The DSC processor hash provided does not match hash of the target file.") + { + this.HResult = ErrorCodes.WinGetConfigProcessorHashMismatch; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs index 3639d2f71c..94653913de 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs @@ -75,5 +75,10 @@ internal static class ErrorCodes /// The property type of a unit is not supported. /// internal const int WinGetConfigUnitUnsupportedType = unchecked((int)0x8A15C112); + + /// + /// The DSC processor hash provided does not match hash of the target file. + /// + internal const int WinGetConfigProcessorHashMismatch = unchecked((int)0x8A15C113); } } diff --git a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs index 9d598b9ba4..55a88fece3 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs @@ -24,6 +24,10 @@ internal sealed partial class DSCv3ConfigurationSetProcessorFactory : Configurat private const string FoundDscExecutablePathPropertyName = "FoundDscExecutablePath"; private const string DiagnosticTraceEnabledPropertyName = "DiagnosticTraceEnabled"; private const string FindDscStateMachinePropertyName = "FindDscStateMachine"; + private const string DscExecutablePathHashPropertyName = "DscExecutablePathHash"; + private const string DscExecutablePathIsAliasPropertyName = "DscExecutablePathIsAlias"; + private const string FoundDscExecutablePathHashPropertyName = "FoundDscExecutablePathHash"; + private const string FoundDscExecutablePathIsAliasPropertyName = "FoundDscExecutablePathIsAlias"; private ProcessorSettings processorSettings = new (); @@ -56,6 +60,49 @@ public string? DscExecutablePath } } + /// + /// Gets or sets the expected SHA256 hash of (hex string). + /// Required when a custom processor path is provided. + /// + public string? DscExecutablePathHash + { + get + { + return this.processorSettings.DscExecutablePathHash; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting DscExecutablePathHash in limit mode is invalid."); + } + + this.processorSettings.DscExecutablePathHash = value; + } + } + + /// + /// Gets or sets a value indicating whether is an app execution alias. + /// + public bool? DscExecutablePathIsAlias + { + get + { + return this.processorSettings.DscExecutablePathIsAlias; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting DscExecutablePathIsAlias in limit mode is invalid."); + } + + this.processorSettings.DscExecutablePathIsAlias = value; + } + } + #if !AICLI_DISABLE_TEST_HOOKS /// /// Gets the processor settings; for tests only. @@ -115,6 +162,14 @@ public bool ContainsKey(string key) { case DscExecutablePathPropertyName: return this.DscExecutablePath != null; + case DscExecutablePathHashPropertyName: + return this.DscExecutablePathHash != null; + case DscExecutablePathIsAliasPropertyName: + return this.DscExecutablePathIsAlias != null; + case FoundDscExecutablePathHashPropertyName: + return this.processorSettings.GetFoundDscExecutablePathHash() != null; + case FoundDscExecutablePathIsAliasPropertyName: + return this.processorSettings.GetFoundDscExecutablePathIsAlias() != null; } return false; @@ -163,6 +218,40 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) case FindDscStateMachinePropertyName: value = this.processorSettings.PumpFindDscStateMachine().ToString(); return true; + case DscExecutablePathHashPropertyName: + if (this.DscExecutablePathHash != null) + { + value = this.DscExecutablePathHash; + return true; + } + + return false; + case DscExecutablePathIsAliasPropertyName: + if (this.DscExecutablePathIsAlias != null) + { + value = this.DscExecutablePathIsAlias.Value.ToString(); + return true; + } + + return false; + case FoundDscExecutablePathHashPropertyName: + string? foundHash = this.processorSettings.GetFoundDscExecutablePathHash(); + if (foundHash != null) + { + value = foundHash; + return true; + } + + return false; + case FoundDscExecutablePathIsAliasPropertyName: + bool? foundIsAlias = this.processorSettings.GetFoundDscExecutablePathIsAlias(); + if (foundIsAlias != null) + { + value = foundIsAlias.Value.ToString(); + return true; + } + + return false; } return false; @@ -202,6 +291,12 @@ private void SetValue(string name, string value) case DiagnosticTraceEnabledPropertyName: this.processorSettings.DiagnosticTraceEnabled = bool.Parse(value); break; + case DscExecutablePathHashPropertyName: + this.DscExecutablePathHash = value; + break; + case DscExecutablePathIsAliasPropertyName: + this.DscExecutablePathIsAlias = bool.Parse(value); + break; default: throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); } diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs index c9aa5ca24a..e1d70a47d5 100644 --- a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs +++ b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs @@ -73,7 +73,7 @@ public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit incomin { try { - this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + this.OnDiagnostics(DiagnosticLevel.Informational, $"CreateUnitProcessor is running in limit mode: {this.IsLimitMode}."); // CreateUnitProcessor can only be called once on each configuration unit in limit mode. var unit = this.GetConfigurationUnit(incomingUnit, true); @@ -172,7 +172,7 @@ protected void OnDiagnostics(DiagnosticLevel level, string message) this.SetProcessorFactory?.OnDiagnostics(level, message); } - private static bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) + private bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) { var firstIdentifier = first.Identifier; var firstIntent = first.Intent; @@ -181,10 +181,15 @@ private static bool ConfigurationUnitEquals(ConfigurationUnit first, Configurati var secondType = second.Type; var secondIntent = second.Intent; - if (firstIdentifier != secondIdentifier || - firstType != secondType || + if (firstIdentifier != secondIdentifier) + { + return false; + } + + if (firstType != secondType || firstIntent != secondIntent) { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of type or intent."); return false; } @@ -194,16 +199,19 @@ private static bool ConfigurationUnitEquals(ConfigurationUnit first, Configurati firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of environment."); return false; } if (!first.Settings.ContentEquals(second.Settings)) { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of settings."); return false; } if (!first.Metadata.ContentEquals(second.Metadata)) { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of metadata."); return false; } @@ -226,7 +234,7 @@ private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, b for (int i = 0; i < unitList.Count; i++) { var unit = unitList[i]; - if (ConfigurationUnitEquals(incomingUnit, unit)) + if (this.ConfigurationUnitEquals(incomingUnit, unit)) { if (useLimitList) { @@ -239,8 +247,8 @@ private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, b // Note: Consider group units logic when group units are supported. } - this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit not found in limit mode."); - throw new InvalidOperationException("Configuration unit not found in limit mode."); + this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit match not found in limit mode."); + throw new InvalidOperationException("Configuration unit match not found in limit mode."); } else { diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs index 3a0e4a1587..506ead01f1 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs @@ -26,6 +26,12 @@ public class Constants /// public const string DynamicRuntimeHandlerIdentifier = "{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; + /// + /// The DSCv3-specific dynamic runtime factory handler identifier. + /// Unlike DynamicRuntimeHandlerIdentifier, this pre-selects the DSCv3 processor engine. + /// + public const string DSCv3DynamicRuntimeHandlerIdentifier = "{5f83e564-ca26-41ca-89db-36f5f0517ffd}"; + /// /// Test guid for enabling test mode for the dynamic runtime factory. Forces factory to exclude 'runas' verb and sets current IL to medium. /// diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs index c0cf67c7dc..85a8f148b4 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs @@ -32,6 +32,7 @@ internal static class Errors public static readonly int WINGET_CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); public static readonly int WINGET_CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); public static readonly int WINGET_CONFIG_ERROR_GET_FAILED = unchecked((int)0x8A15C011); + public static readonly int WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY = unchecked((int)0x8A15C013); // Configuration Processor Errors public static readonly int WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); @@ -46,7 +47,7 @@ internal static class Errors public static readonly int WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT = unchecked((int)0x8A15C110); public static readonly int WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN = unchecked((int)0x8A15C111); public static readonly int WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR = unchecked((int)0x8A15C112); - public static readonly int WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY = unchecked((int)0x8A15C013); + public static readonly int WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH = unchecked((int)0x8A15C113); // Limitation Set Errors public static readonly int CORE_INVALID_OPERATION = unchecked((int)0x80131509); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs new file mode 100644 index 0000000000..f8c12c9e57 --- /dev/null +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs @@ -0,0 +1,108 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Integration tests for DSCv3 processor path integrity checks. + /// These tests verify that hash verification catches mismatches when a custom DSC executable + /// path is provided. Units run in-process (no elevation split) so limit mode is not involved. + /// + [Collection("UnitTestCollection")] + [OutOfProc] + public class DSCv3ProcessorPathIntegrityIntegrationTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DSCv3ProcessorPathIntegrityIntegrationTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Verifies that providing a wrong hash for a custom processor path causes the + /// apply to fail with a hash mismatch error. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Apply_ProcessorPath_WrongHash_FailsWithHashMismatch() + { + using var tempFile = new TempFile(content: "test content for hash test"); + + IConfigurationSetProcessorFactory dynamicFactory = + await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( + Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); + + var factoryMap = (IDictionary)dynamicFactory; + factoryMap["DscExecutablePath"] = tempFile.FullFileName; + factoryMap["DscExecutablePathHash"] = new string('0', 64); // Wrong hash. + factoryMap["DscExecutablePathIsAlias"] = "false"; + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Identifier = "testUnit"; + unit.Type = "TestResource/TestUnit"; + unit.Intent = ConfigurationUnitIntent.Apply; + configurationSet.Units = new[] { unit }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + var ex = Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + + /// + /// Verifies that omitting the hash for a custom processor path causes the apply + /// to fail. The server requires a hash whenever a custom path is provided. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Apply_ProcessorPath_MissingHash_ApplyFails() + { + using var tempFile = new TempFile(content: "test content"); + + IConfigurationSetProcessorFactory dynamicFactory = + await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( + Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); + + var factoryMap = (IDictionary)dynamicFactory; + + // DscExecutablePathHash intentionally omitted. + factoryMap["DscExecutablePath"] = tempFile.FullFileName; + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Identifier = "testUnit"; + unit.Type = "TestResource/TestUnit"; + unit.Intent = ConfigurationUnitIntent.Apply; + configurationSet.Units = new[] { unit }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs new file mode 100644 index 0000000000..2358eb7ee1 --- /dev/null +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs @@ -0,0 +1,160 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// In-process unit tests for DSCv3 processor path integrity helper methods. + /// These exercise the helpers directly without going through the elevation split. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DSCv3ProcessorPathIntegrityUnitTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DSCv3ProcessorPathIntegrityUnitTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Verifies that returns a 64-char + /// lowercase hex SHA256 hash for a regular file. + /// + [Fact] + public void ComputeHash_RegularFile_ReturnsLowercaseHex64() + { + using var tempFile = new TempFile(content: "test content for hashing"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + + Assert.False(isAlias); + Assert.Equal(64, hash.Length); + Assert.Equal(hash, hash.ToLowerInvariant()); + } + + /// + /// Verifies that two calls to on the same + /// file return the same hash. + /// + [Fact] + public void ComputeHash_SameFile_ReturnsSameHash() + { + using var tempFile = new TempFile(content: "deterministic content"); + + string hash1 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); + string hash2 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); + + Assert.Equal(hash1, hash2); + } + + /// + /// Verifies that succeeds and returns a + /// valid handle when the correct hash is supplied. + /// + [Fact] + public void VerifyAndOpen_CorrectHash_ReturnsValidHandle() + { + using var tempFile = new TempFile(content: "test content"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, hash, isAlias); + + Assert.False(handle.IsInvalid); + } + + /// + /// Verifies that throws with the + /// hash mismatch HRESULT when the wrong hash is supplied. + /// + [Fact] + public void VerifyAndOpen_WrongHash_ThrowsHashMismatch() + { + using var tempFile = new TempFile(content: "test content"); + + Exception? ex = Record.Exception(() => + { + using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, new string('0', 64), isAlias: false); + }); + + Assert.NotNull(ex); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + + /// + /// Verifies that throws + /// when a custom path is set without a hash. + /// + [Fact] + public void ProcessorSettings_CustomPath_NoHash_Throws() + { + using var tempFile = new TempFile(content: "test content"); + + var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + + Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); + + Assert.NotNull(ex); + Assert.IsType(ex); + } + + /// + /// Verifies that returns the + /// path when the correct hash is provided. + /// + [Fact] + public void ProcessorSettings_CustomPath_CorrectHash_ReturnsPath() + { + using var tempFile = new TempFile(content: "test content"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + + using var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + settings.DscExecutablePathHash = hash; + settings.DscExecutablePathIsAlias = isAlias; + + Assert.Equal(tempFile.FullFileName, settings.EffectiveDscExecutablePath); + } + + /// + /// Verifies that throws with the + /// hash mismatch HRESULT when a wrong hash is set for a custom path. + /// + [Fact] + public void ProcessorSettings_CustomPath_WrongHash_ThrowsHashMismatch() + { + using var tempFile = new TempFile(content: "test content"); + + var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + settings.DscExecutablePathHash = new string('0', 64); + settings.DscExecutablePathIsAlias = false; + + Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); + + Assert.NotNull(ex); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs index 32814f8a80..4e56765212 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs @@ -6,8 +6,12 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { + using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Reflection; + using System.Security.Cryptography; using Microsoft.Management.Configuration.Processor; using Microsoft.Management.Configuration.Processor.DSCv3.Model; using Microsoft.Management.Configuration.Processor.Exceptions; @@ -313,7 +317,10 @@ private static (DSCv3ConfigurationSetProcessorFactory, TestDSCv3) CreateTestFact DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); TestDSCv3 dsc = new TestDSCv3(); factory.Settings.DSCv3 = dsc; - factory.Settings.DscExecutablePath = "Test-Path-Not-Used.txt"; + TempFile tempFile = new TempFile(content: "Contents", cleanup: false); + factory.Settings.DscExecutablePath = tempFile.FullFileName; + using var fileStream = File.Open(factory.Settings.DscExecutablePath, FileMode.Open); + factory.Settings.DscExecutablePathHash = Convert.ToHexString(SHA256.HashData(fileStream)).ToLowerInvariant(); return (factory, dsc); }