diff --git a/cbsexploder/cbsexploder.cpp b/cbsexploder/cbsexploder.cpp index 896ef9f..5d50ba0 100644 --- a/cbsexploder/cbsexploder.cpp +++ b/cbsexploder/cbsexploder.cpp @@ -1,6 +1,4 @@ -#include "CbsApi.h" -#include "options.h" -#include "logging.h" +#include "uihandler.h" #include #include #include @@ -52,14 +50,11 @@ int LoadWds() { return 0; } -int ProcessOptions(int argc, LPCWSTR* argv) { +int ProcessOptions(int argc, LPCWSTR* argv, BOOL isCmdline) { for (int i = 0; i < argc; i++) { LPCWSTR arg = argv[i]; - - if (!wcsncmp(arg, L"/dbg", 4)) { - g_options.debug = TRUE; - } - else if (!wcsncmp(arg, L"/sp", 3)) { + + if (!wcsncmp(arg, L"/sp", 3)) { ASRT_NL(g_options.intendedPkgState == CbsInstallState::Unknown, "ERROR: Multiple package operations specified"); g_options.intendedPkgState = CbsInstallState::Staged; } @@ -71,74 +66,99 @@ int ProcessOptions(int argc, LPCWSTR* argv) { ASRT_NL(g_options.intendedPkgState == CbsInstallState::Unknown, "ERROR: Multiple package operations specified"); g_options.intendedPkgState = CbsInstallState::Absent; } - else if (!wcsncmp(arg, L"/ep", 3)) { - ASRT_NL(g_options.intendedPkgState == CbsInstallState::Unknown, "ERROR: Option /ep is incompatible with package operations"); - g_options.noAct = TRUE; - } - else if (!wcsncmp(arg, L"/o", 2)) { - LPCWSTR val = wcschr(arg, L':'); - ASRT_NL(val, "ERROR: Invalid argument %s", arg); - g_options.winDir = val + 1; - } else if (!wcsncmp(arg, L"/m", 2)) { LPCWSTR val = wcschr(arg, L':'); ASRT_NL(val, "ERROR: Invalid argument %s", arg); g_options.pkgPath = val + 1; } - else if (!wcsncmp(arg, L"/log", 4)) { - LPCWSTR val = wcschr(arg, L':'); - ASRT_NL(val, "ERROR: Invalid argument %s", arg); - g_options.logPath = val + 1; - } - else if (!wcsncmp(arg, L"/ls", 3)) { - g_options.localStack = TRUE; + + if (isCmdline) { + if (!wcsncmp(arg, L"/ep", 3)) { + g_options.enumPkgs = TRUE; + } + else if (!wcsncmp(arg, L"/dbg", 4)) { + g_options.debug = TRUE; + } + else if (!wcsncmp(arg, L"/o", 2)) { + LPCWSTR val = wcschr(arg, L':'); + ASRT_NL(val, "ERROR: Invalid argument %s", arg); + g_options.winDir = val + 1; + } + else if (!wcsncmp(arg, L"/log", 4)) { + LPCWSTR val = wcschr(arg, L':'); + ASRT_NL(val, "ERROR: Invalid argument %s", arg); + g_options.logPath = val + 1; + } + else if (!wcsncmp(arg, L"/b", 2)) { + LPCWSTR val = wcschr(arg, L':'); + ASRT_NL(val, "ERROR: Invalid argument %s", arg); + g_options.batchPath = val + 1; + } + else if (!wcsncmp(arg, L"/?", 2)) { + g_options.help = TRUE; + } } } ASRT_NL(g_options.winDir, "ERROR: No offline Windows directory path specified"); - ASRT_NL(g_options.intendedPkgState != CbsInstallState::Unknown, "ERROR: No package operation specified"); - ASRT_NL(g_options.pkgPath, "ERROR: No package path specified"); + + if (!g_options.batchPath) { + if (!g_options.enumPkgs) { + ASRT_NL(g_options.intendedPkgState != CbsInstallState::Unknown, "ERROR: No package operation specified"); + ASRT_NL(g_options.pkgPath, "ERROR: No package path specified"); + } + } + else { + ASRT_NL(!g_options.enumPkgs, "ERROR: Batch file cannot be used with /ep") + + if (isCmdline) { + ASRT_NL(g_options.intendedPkgState == CbsInstallState::Unknown, "ERROR: Batch file cannot be used with /sp, /ip, or /up"); + ASRT_NL(!g_options.pkgPath, "ERROR: Batch file cannot be used with /m option"); + } + } return 0; } -LPCWSTR PkgStateAsStr(CbsInstallState state) { - switch (state) { - case CbsInstallState::Absent: - return L"Uninstalled"; - break; - case CbsInstallState::Installed: - return L"Installed"; - break; - case CbsInstallState::PartiallyInstalled: - return L"Partially Installed"; - break; - case CbsInstallState::Staged: - return L"Staged"; - break; - case CbsInstallState::Invalid: - return L"Invalid"; - break; - case CbsInstallState::Invalid_Installed: - return L"Invalid Installed"; - break; - case CbsInstallState::Invalid_Staged: - return L"Invalid Staged"; - break; - case CbsInstallState::Unknown: - default: - return L"Unknown"; - break; - } +void PrintUsage(LPCWSTR exename) { + wprintf( + L"\n" + "Usage: %s / /m: /o: [/b:] [/dbg] [/?]\n" + "\nModes:\n" + "/sp\tStage package\n" + "/ip\tInstall package\n" + "/up\tUninstall package\n" + "/ep\tEnumerate installed packages (overrides other arguments)\n" + "\nSession arguments:\n" + "/m\tManifest path for package\n" + "/o\tOffline image Windows directory path\n" + "\nOptional:\n" + "/dbg\tEnable debug mode (more verbose output and logging)\n" + "/?\tPrint this usage guide" + "/b\tUse batch file\n" + "\nBatch file:\n" + "/b argument cannot be used with /sp, /ip, /up, /ep, or /m.\n" + "Each line in batch file represents one package operation, in the same format as the above arguments.\n\n" + "Example batch file:\n" + "/ip /m:C:\\pkg1.mum\n" + "/sp /m:C:\\pkg2.mum\n", + exename + ); } int wmain(int argc, LPCWSTR* argv) { - printf("cbsexploder 0.0.1 by witherornot\n"); + printf("cbsexploder 0.0.2 by witherornot\n"); - if (ProcessOptions(argc, argv)) { + if (ProcessOptions(argc, argv, TRUE)) { + PrintUsage(argv[0]); return 1; } + + if (g_options.help) { + PrintUsage(argv[0]); + return 0; + } WCHAR fullLogPath[MAX_PATH]; ASRT_NL(GetFullPathNameW(g_options.logPath, MAX_PATH, fullLogPath, NULL), "ERROR: Log path %s is invalid", g_options.logPath); @@ -174,32 +194,103 @@ int wmain(int argc, LPCWSTR* argv) CHK_HR(cbsSess->Initialize(CbsSessionOption::None, L"CbsExploder", bootDrive, winDir), "ERROR: Failed to initialize CBS Session (bootDrive = \"%s\", winDir = \"%s\") [HR = %08x]", bootDrive, winDir); LDBG("Initialized CBS Session (bootDrive = \"%s\", winDir = \"%s\")", bootDrive, winDir); + + if (g_options.enumPkgs) { + ComPtr pkgEnum; + CHK_HR(cbsSess->EnumeratePackages(0x50, &pkgEnum), "ERROR: Failed to enumerate packages"); - WCHAR fullPkgPath[MAX_PATH]; - ASRT_NL(GetFullPathNameW(g_options.pkgPath, MAX_PATH, fullPkgPath, NULL), "Package path %s is invalid", g_options.pkgPath); + UINT pbFetched = 1; + ComPtr pkgIdent; - ComPtr pkg; - CHK_HR(cbsSess->CreatePackage(0, CbsPackageType::ExpandedWithMum, fullPkgPath, NULL, &pkg), "ERROR: Failed to create package (pkgPath = \"%s\") [HR = %08x]", fullPkgPath); + while (pbFetched) { + HRESULT result = pkgEnum->Next(1, &pkgIdent, &pbFetched); - LPWSTR pkgIdent; - pkg->GetProperty(CbsPackageProperty::IdentityString, &pkgIdent); + if (SUCCEEDED(result) && pbFetched) { + PWSTR identName; + pkgIdent->GetStringId(&identName); + ConLog("%s", identName); + } + } - LDBG("Loaded package %s", pkgIdent); + return 0; + } - CbsInstallState curState, appState; - CHK_HR(pkg->EvaluateApplicability(0, &appState, &curState), "ERROR: Failed to evaluate applicability for %s [HR = %08x]", pkgIdent); - - LPCWSTR curStateStr = PkgStateAsStr(curState); - LPCWSTR intendStateStr = PkgStateAsStr(g_options.intendedPkgState); + FILE* batchFile = NULL; + + if (g_options.batchPath) { + _wfopen_s(&batchFile, g_options.batchPath, L"r"); + ASRT(batchFile, "ERROR: Failed to open batch file %s", g_options.batchPath); + } + + while (1) { + if (batchFile) { + WCHAR line[2048] = { 0 }; + fgetws(line, 2048, batchFile); + + if (!wcslen(line)) { + break; + } + + LPWSTR* bargv = (LPWSTR*)malloc(8 * sizeof(LPWSTR)); + int bargc = 0; + + WCHAR* tokCtx = NULL; + WCHAR* token = NULL; + LPCWSTR delims = L" \t\n"; + + token = wcstok_s(line, delims, &tokCtx); + while (token) { + bargv[bargc] = (WCHAR*)malloc((wcslen(token) + 1) * sizeof(WCHAR*)); + ASRT(bargv[bargc], "ERROR: Failed to allocate batch file argument"); + memset(bargv[bargc], 0, (wcslen(token) + 1) * sizeof(WCHAR*)); + memcpy(bargv[bargc], token, sizeof(WCHAR) * wcslen(token)); + bargc += 1; + + token = wcstok_s(NULL, delims, &tokCtx); + } + + if (ProcessOptions(bargc, (LPCWSTR*)bargv, FALSE)) { + return 1; + } - LDBG("Package current state: %s", curStateStr); + for (int i = 0; i < bargc; i++) free(bargv[i]); + free(bargv); + } + + WCHAR fullPkgPath[MAX_PATH]; + ASRT_NL(GetFullPathNameW(g_options.pkgPath, MAX_PATH, fullPkgPath, NULL), "Package path %s is invalid", g_options.pkgPath); + + ComPtr pkg; + CHK_HR(cbsSess->CreatePackage(0, CbsPackageType::ExpandedWithMum, fullPkgPath, NULL, &pkg), "ERROR: Failed to create package (pkgPath = \"%s\") [HR = %08x]", fullPkgPath) + + LPWSTR pkgIdent; + pkg->GetProperty(CbsPackageProperty::IdentityString, &pkgIdent); + + LDBG("Loaded package %s", pkgIdent); + + CbsInstallState curState, appState; + CHK_HR(pkg->EvaluateApplicability(0, &appState, &curState), "ERROR: Failed to evaluate applicability for %s [HR = %08x]", pkgIdent); + + LPCWSTR curStateStr = PkgStateAsStr(curState); + LPCWSTR intendStateStr = PkgStateAsStr(g_options.intendedPkgState); - ASRT(curState != g_options.intendedPkgState, "ERROR: Package %s is already %s", pkgIdent, curStateStr); - ASRT(appState == CbsInstallState::Installed, "ERROR: Package %s is not applicable to the current image", pkgIdent); + LDBG("Package current state: %s", curStateStr); - LOG("Setting package %s as %s", pkgIdent, intendStateStr); + ASRT(curState != g_options.intendedPkgState, "ERROR: Package %s is already %s", pkgIdent, curStateStr); + ASRT(appState == CbsInstallState::Installed, "ERROR: Package %s is not applicable to the current image", pkgIdent); - pkg->InitiateChanges(0, CbsInstallState::Installed, NULL); + LOG("Setting package %s as %s", pkgIdent, intendStateStr); + + const ComPtr pHandler = new UIHandler(); + pkg->InitiateChanges(0, g_options.intendedPkgState, pHandler.Get()); + + if (!batchFile) { + break; + } + else { + g_options.intendedPkgState = CbsInstallState::Unknown; + } + } CbsRequiredAction reqAct; CHK_HR(cbsSess->FinalizeEx(0, &reqAct), "ERROR: Failed to finalize CBS session [HR = %08x]"); diff --git a/cbsexploder/cbsexploder.vcxproj b/cbsexploder/cbsexploder.vcxproj index 9cccccb..525ea12 100644 --- a/cbsexploder/cbsexploder.vcxproj +++ b/cbsexploder/cbsexploder.vcxproj @@ -142,11 +142,13 @@ + + diff --git a/cbsexploder/logging.cpp b/cbsexploder/logging.cpp index 1c62c58..1741333 100644 --- a/cbsexploder/logging.cpp +++ b/cbsexploder/logging.cpp @@ -36,4 +36,70 @@ HRESULT WdsLog(WdsLogSource source, WdsLogLevel level, LPCSTR fmt, ...) { va_end(va_args); return result; +} + +LPCWSTR PkgStateAsStr(CbsInstallState state) { + switch (state) { + case CbsInstallState::Absent: + return L"Uninstalled"; + case CbsInstallState::UninstallRequested: + return L"Uninstall Requested"; + case CbsInstallState::Installed: + return L"Installed"; + case CbsInstallState::PartiallyInstalled: + return L"Partially Installed"; + case CbsInstallState::InstallRequested: + return L"Install Requested"; + case CbsInstallState::Staging: + return L"Staging"; + case CbsInstallState::Resolved: + return L"Resolved"; + case CbsInstallState::Resolving: + return L"Resolving"; + case CbsInstallState::Staged: + return L"Staged"; + case CbsInstallState::Superseded: + return L"Superseded"; + case CbsInstallState::Permanent: + return L"Permanent"; + case CbsInstallState::Invalid_Permanent: + return L"Invalid Permanent"; + case CbsInstallState::Cancel: + return L"Cancel"; + case CbsInstallState::Default: + return L"Default"; + case CbsInstallState::Invalid: + return L"Invalid"; + case CbsInstallState::Invalid_Installed: + return L"Invalid Installed"; + case CbsInstallState::Invalid_Staged: + return L"Invalid Staged"; + case CbsInstallState::Unknown: + default: + return L"Unknown"; + } +} + +LPCWSTR CbsOpStageAsStr(CbsOperationStage stage) { + switch (stage) { + case CbsOperationStage::Waiting: + return L"Waiting"; + case CbsOperationStage::Planning: + return L"Planning"; + case CbsOperationStage::Downloading: + return L"Downloading"; + case CbsOperationStage::Extracting: + return L"Extracting"; + case CbsOperationStage::Resolving: + return L"Extracting"; + case CbsOperationStage::Staging: + return L"Staging"; + case CbsOperationStage::Installing: + case CbsOperationStage::InstallingEx: + return L"Installing"; + case CbsOperationStage::ReservicingLCU: + return L"Reservicing LCU"; + default: + return L"Unknown"; + } } \ No newline at end of file diff --git a/cbsexploder/logging.h b/cbsexploder/logging.h index 7bdf282..f8199e0 100644 --- a/cbsexploder/logging.h +++ b/cbsexploder/logging.h @@ -14,4 +14,6 @@ #define ASRT_NL(cond,fmt,...) if(!(cond)) {ConLog(fmt,##__VA_ARGS__); return 1;} HRESULT WdsLog(WdsLogSource source, WdsLogLevel level, LPCSTR fmt, ...); -void ConLog(LPCSTR fmt, ...); \ No newline at end of file +void ConLog(LPCSTR fmt, ...); +LPCWSTR PkgStateAsStr(CbsInstallState state); +LPCWSTR CbsOpStageAsStr(CbsOperationStage stage); \ No newline at end of file diff --git a/cbsexploder/options.h b/cbsexploder/options.h index 9c72c71..992e5eb 100644 --- a/cbsexploder/options.h +++ b/cbsexploder/options.h @@ -9,7 +9,8 @@ typedef struct _OPTIONS { BOOL debug = FALSE; BOOL localStack = FALSE; - BOOL noAct = FALSE; + BOOL enumPkgs = FALSE; + BOOL help = FALSE; LPCWSTR batchPath = NULL; LPCWSTR pkgPath = NULL; LPCWSTR logPath = L"C:\\cbsexploder.log"; diff --git a/cbsexploder/uihandler.cpp b/cbsexploder/uihandler.cpp new file mode 100644 index 0000000..97e9180 --- /dev/null +++ b/cbsexploder/uihandler.cpp @@ -0,0 +1,65 @@ +#include "uihandler.h" + +ULONG UIHandler::AddRef() { + return InterlockedIncrement(&dwRef); +} + +HRESULT UIHandler::QueryInterface(REFIID riid, LPVOID* ppvObject) { + *ppvObject = NULL; + + if (riid == __uuidof(ICbsUIHandler) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + this->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +ULONG UIHandler::Release() { + ULONG decRef = InterlockedDecrement(&dwRef); + + if (!decRef) { + delete this; + } + + return decRef; +} + +HRESULT UIHandler::Initiate(IEnumCbsUpdate* updates, int* pUnk) { + return S_OK; +} + +HRESULT UIHandler::Error(HRESULT hr, PCWSTR str, int* pUnk) { + ConLog("ERROR: %s [HR = %08x]", str, hr); + return S_OK; +} + +HRESULT UIHandler::ResolveSource(PCWSTR str1, ICbsIdentity* pIdent, PCWSTR str2, PWSTR* strUnk, int* pUnk) { + if (g_options.debug) { + PWSTR identStr; + pIdent->SaveAsStringId(&identStr); + ConLog("Resolve Source: (pkg = \"%s\") \"%s\" \"%s\"", identStr, str1, str2); + } + + return S_OK; +} + +HRESULT UIHandler::EnteringStage(UINT unk1, CbsOperationStage stage, int unk2, int unk3) { + ConLog("Entering Stage: %s", CbsOpStageAsStr(stage)); + return S_OK; +} + +HRESULT UIHandler::Progress(CbsInstallState state, UINT cur, UINT total, int* pUnk) { + ConLog("Progress: %.1f%% [Current Status: %s]", 100.0 * cur / total, PkgStateAsStr(state)); + return S_OK; +} + +HRESULT UIHandler::ProgressEx(CbsInstallState state, UINT cur, UINT total, UINT unk, int* pUnk) { + return Progress(state, cur, total, pUnk); +} + +HRESULT UIHandler::Terminate() { + ConLog("Operations finished, waiting for cleanup..."); + return S_OK; +} \ No newline at end of file diff --git a/cbsexploder/uihandler.h b/cbsexploder/uihandler.h new file mode 100644 index 0000000..f2a645a --- /dev/null +++ b/cbsexploder/uihandler.h @@ -0,0 +1,23 @@ +#include "CbsApi.h" +#include "logging.h" + +class UIHandler : public ICbsUIHandler { +private: + ULONG dwRef = 0; + +public: + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObject) override; + virtual ULONG STDMETHODCALLTYPE AddRef(void) override; + virtual ULONG STDMETHODCALLTYPE Release(void) override; + + virtual HRESULT STDMETHODCALLTYPE Initiate(IEnumCbsUpdate*, int*) override; + virtual HRESULT STDMETHODCALLTYPE Terminate(void) override; + virtual HRESULT STDMETHODCALLTYPE Error(HRESULT, PCWSTR, int*) override; + virtual HRESULT STDMETHODCALLTYPE ResolveSource(PCWSTR, ICbsIdentity*, PCWSTR, PWSTR*, int*) override; + virtual HRESULT STDMETHODCALLTYPE Progress(CbsInstallState, UINT, UINT, int*) override; + virtual HRESULT STDMETHODCALLTYPE EnteringStage(UINT, CbsOperationStage, int, int) override; + virtual HRESULT STDMETHODCALLTYPE ProgressEx(CbsInstallState, UINT, UINT, UINT, int*) override; + + UIHandler() = default; + virtual ~UIHandler() = default; +}; \ No newline at end of file