TestCommand.cpp (13175B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 5 #ifndef AICLI_DISABLE_TEST_HOOKS 6 7 #include "TestCommand.h" 8 #include "AppInstallerRuntime.h" 9 #include "TableOutput.h" 10 #include "Public/ConfigurationSetProcessorFactoryRemoting.h" 11 #include "Public/ShutdownMonitoring.h" 12 #include "Workflows/ConfigurationFlow.h" 13 #include "Workflows/MSStoreInstallerHandler.h" 14 #include <winget/RepositorySource.h> 15 #include <winrt/Microsoft.Management.Configuration.h> 16 17 using namespace AppInstaller::CLI::Workflow; 18 using namespace AppInstaller::Utility::literals; 19 20 namespace AppInstaller::CLI 21 { 22 namespace 23 { 24 void LogAndReport(Execution::Context& context, std::string_view message) 25 { 26 context.Reporter.Info() << message << std::endl; 27 AICLI_LOG(CLI, Info, << message); 28 } 29 30 HRESULT WaitForShutdown(Execution::Context& context) 31 { 32 LogAndReport(context, "Waiting for app shutdown event"); 33 if (!ShutdownMonitoring::TerminationSignalHandler::Instance()->WaitForAppShutdownEvent()) 34 { 35 LogAndReport(context, "Failed getting app shutdown event"); 36 return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; 37 } 38 39 LogAndReport(context, "Succeeded waiting for app shutdown event"); 40 return S_OK; 41 } 42 43 HRESULT AppShutdownWindowMessage(Execution::Context& context) 44 { 45 auto windowHandle = ShutdownMonitoring::TerminationSignalHandler::Instance()->GetWindowHandle(); 46 47 if (windowHandle == NULL) 48 { 49 LogAndReport(context, "Window was not created"); 50 return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; 51 } 52 53 if (context.Args.Contains(Execution::Args::Type::Force)) 54 { 55 LogAndReport(context, "Sending WM_QUERYENDSESSION message"); 56 THROW_LAST_ERROR_IF(!SendMessageTimeout( 57 windowHandle, 58 WM_QUERYENDSESSION, 59 NULL, 60 ENDSESSION_CLOSEAPP, 61 (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), 62 5000, 63 NULL)); 64 } 65 66 HRESULT hr = WaitForShutdown(context); 67 68 if (context.Args.Contains(Execution::Args::Type::Force)) 69 { 70 LogAndReport(context, "Sending WM_ENDSESSION message"); 71 THROW_LAST_ERROR_IF(!SendMessageTimeout( 72 windowHandle, 73 WM_ENDSESSION, 74 NULL, 75 ENDSESSION_CLOSEAPP, 76 (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), 77 5000, 78 NULL)); 79 } 80 81 return hr; 82 } 83 84 void EnsureDSCv3Processor(Execution::Context& context) 85 { 86 auto& configurationSet = context.Get<Execution::Data::ConfigurationContext>().Set(); 87 configurationSet.Environment().ProcessorIdentifier(L"dscv3"); 88 } 89 90 void InvokeGetAllUnits(Execution::Context& context) 91 { 92 auto& configurationContext = context.Get<Execution::Data::ConfigurationContext>(); 93 94 winrt::Microsoft::Management::Configuration::ConfigurationUnit unit; 95 unit.Type(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ConfigurationExportResource))); 96 97 auto result = configurationContext.Processor().GetAllUnits(unit); 98 99 if (FAILED(result.ResultInformation().ResultCode())) 100 { 101 context.Reporter.Error() << "Failed to export: " << WINGET_OSTREAM_FORMAT_HRESULT(result.ResultInformation().ResultCode()) << std::endl; 102 AICLI_TERMINATE_CONTEXT(result.ResultInformation().ResultCode()); 103 } 104 105 for (const auto& resultUnit : result.Units()) 106 { 107 configurationContext.Set().Units().Append(resultUnit); 108 } 109 } 110 111 // Command to directly invoke the export flow. 112 struct TestConfigurationExportCommand final : public Command 113 { 114 TestConfigurationExportCommand(std::string_view parent) : Command("config-export-units", {}, parent) {} 115 116 std::vector<Argument> GetArguments() const override 117 { 118 return { 119 Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, 120 Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, 121 }; 122 } 123 124 Resource::LocString ShortDescription() const override 125 { 126 return "Run config export"_lis; 127 } 128 129 Resource::LocString LongDescription() const override 130 { 131 return "Runs the GetAllUnits configuration method to test export on a DSC v3 directly."_lis; 132 } 133 134 protected: 135 void ExecuteInternal(Execution::Context& context) const override 136 { 137 context << 138 VerifyIsFullPackage << 139 CreateConfigurationProcessorWithoutFactory << 140 CreateOrOpenConfigurationSet{ "0.3" } << 141 EnsureDSCv3Processor << 142 CreateConfigurationProcessor << 143 InvokeGetAllUnits << 144 WriteConfigFile; 145 } 146 }; 147 148 void InvokeFindUnitProcessors(Execution::Context& context) 149 { 150 auto& configurationContext = context.Get<Execution::Data::ConfigurationContext>(); 151 152 winrt::Microsoft::Management::Configuration::FindUnitProcessorsOptions findOptions; 153 154 if (context.Args.Contains(Execution::Args::Type::InstallLocation)) 155 { 156 findOptions.SearchPaths(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::InstallLocation))); 157 findOptions.SearchPathsExclusive(true); 158 findOptions.UnitDetailFlags(winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags::Local); 159 } 160 161 auto result = configurationContext.Processor().FindUnitProcessors(findOptions); 162 163 if (result.Size() > 0) 164 { 165 Execution::TableOutput<2> table(context.Reporter, 166 { 167 "Type"_lis, 168 "Description"_lis 169 }); 170 171 for (const auto& resultUnitProcessor : result) 172 { 173 table.OutputLine({ 174 Utility::ConvertToUTF8(resultUnitProcessor.UnitType()), 175 Utility::ConvertToUTF8(resultUnitProcessor.UnitDescription()) 176 }); 177 } 178 179 table.Complete(); 180 } 181 else 182 { 183 context.Reporter.Info() << "No unit processors found."_lis << std::endl; 184 } 185 } 186 187 // Command to directly invoke find unit processors. 188 struct TestConfigurationFindUnitProcessorsCommand final : public Command 189 { 190 TestConfigurationFindUnitProcessorsCommand(std::string_view parent) : Command("config-find-unit-processors", {}, parent) {} 191 192 std::vector<Argument> GetArguments() const override 193 { 194 return { 195 Argument{ Execution::Args::Type::InstallLocation, Resource::String::LocationArgumentDescription }, 196 }; 197 } 198 199 Resource::LocString ShortDescription() const override 200 { 201 return "Run find unit processors"_lis; 202 } 203 204 Resource::LocString LongDescription() const override 205 { 206 return "Runs find unit processors. Search paths could be provided."_lis; 207 } 208 209 protected: 210 void ExecuteInternal(Execution::Context& context) const override 211 { 212 context << 213 VerifyIsFullPackage << 214 CreateConfigurationProcessorWithoutFactory << 215 CreateOrOpenConfigurationSet{ "0.3" } << 216 EnsureDSCv3Processor << 217 CreateConfigurationProcessor << 218 InvokeFindUnitProcessors; 219 } 220 }; 221 222 struct TestCanUnloadNowCommand final : public Command 223 { 224 TestCanUnloadNowCommand(std::string_view parent) : Command("can-unload-now", {}, parent, Visibility::Hidden) {} 225 226 Resource::LocString ShortDescription() const override 227 { 228 return "Test DllCanUnloadNow"_lis; 229 } 230 231 Resource::LocString LongDescription() const override 232 { 233 return "Verifies that the function that implements the inproc DllCanUnloadNow properly blocks unload due to static storage object."_lis; 234 } 235 236 protected: 237 void ExecuteInternal(Execution::Context& context) const override 238 { 239 Repository::Source source{ Repository::PredefinedSource::Installed }; 240 241 ProgressCallback progress; 242 source.Open(progress); 243 244 HMODULE self = GetModuleHandle(L"WindowsPackageManager.dll"); 245 if (!self) 246 { 247 LogAndReport(context, "Couldn't get WindowsPackageManager module"); 248 return; 249 } 250 251 auto WindowsPackageManagerInProcModuleTerminate = reinterpret_cast<bool (__stdcall *)()>(GetProcAddress(self, "WindowsPackageManagerInProcModuleTerminate")); 252 253 // Report the object counts, attempt to terminate, report the object counts again 254 ReportObjectCounts(context); 255 LogAndReport(context, WindowsPackageManagerInProcModuleTerminate() ? "DllCanUnloadNow" : "DllCannotUnloadNow"); 256 ReportObjectCounts(context); 257 } 258 259 private: 260 void ReportObjectCounts(Execution::Context& context) const 261 { 262 std::ostringstream stream; 263 stream << "Internal objects: " << GetInternalObjectCount() << '\n'; 264 stream << "External objects: " << GetExternalObjectCount(); 265 266 LogAndReport(context, stream.str()); 267 } 268 269 uint32_t GetInternalObjectCount() const 270 { 271 return winrt::get_module_lock().operator unsigned int(); 272 } 273 274 unsigned long GetExternalObjectCount() const 275 { 276 auto module = Microsoft::WRL::GetModuleBase(); 277 return module ? module->GetObjectCount() : 0; 278 } 279 }; 280 } 281 282 std::vector<std::unique_ptr<Command>> TestCommand::GetCommands() const 283 { 284 return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>( 285 { 286 std::make_unique<TestAppShutdownCommand>(FullName()), 287 std::make_unique<TestConfigurationExportCommand>(FullName()), 288 std::make_unique<TestConfigurationFindUnitProcessorsCommand>(FullName()), 289 std::make_unique<TestCanUnloadNowCommand>(FullName()), 290 }); 291 } 292 293 void TestCommand::ExecuteInternal(Execution::Context& context) const 294 { 295 UNREFERENCED_PARAMETER(context); 296 Sleep(INFINITE); 297 } 298 299 Resource::LocString TestCommand::ShortDescription() const 300 { 301 return Utility::LocIndString("Waits infinitely"sv); 302 } 303 304 Resource::LocString TestCommand::LongDescription() const 305 { 306 return Utility::LocIndString("Waits infinitely. Use this if you want winget to wait forever while something is going on"sv); 307 } 308 309 std::vector<Argument> TestAppShutdownCommand::GetArguments() const 310 { 311 return { 312 Argument::ForType(Execution::Args::Type::Force) 313 }; 314 } 315 316 void TestAppShutdownCommand::ExecuteInternal(Execution::Context& context) const 317 { 318 HRESULT hr = E_FAIL; 319 320 // Only package context and admin won't create the window message. 321 if (!Runtime::IsRunningInPackagedContext() || !Runtime::IsRunningAsAdmin()) 322 { 323 hr = AppShutdownWindowMessage(context); 324 } 325 else 326 { 327 hr = WaitForShutdown(context); 328 } 329 330 AICLI_TERMINATE_CONTEXT(hr); 331 } 332 333 Resource::LocString TestAppShutdownCommand::ShortDescription() const 334 { 335 return Utility::LocIndString("Test command to verify appshutdown event."sv); 336 } 337 338 Resource::LocString TestAppShutdownCommand::LongDescription() const 339 { 340 return Utility::LocIndString("Test command for appshutdown. Verifies the window was created and waits for the app shutdown event"sv); 341 } 342 343 } 344 345 #endif