diff --git a/.gitignore b/.gitignore index 1bc915c..de7a209 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,6 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store + +# Vc project file +*.VC.opendb \ No newline at end of file diff --git a/src/Helpers.cpp b/src/Helpers.cpp new file mode 100644 index 0000000..6886981 --- /dev/null +++ b/src/Helpers.cpp @@ -0,0 +1,144 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// + +#include "stdafx.h" +#include "Helpers.h" +#include +#include +#include +#include +#include +#include + +#define IDM_STARTDIAGNOSTICSMODE 3802 +#define CP_AUTO 50001 +#define VERSION_SIGNATURE 0xFEEF04BD + +namespace Helpers +{ + BOOL EnumWindowsHelper(_In_ const function& callbackFunc) + { + return ::EnumWindows([](HWND hwnd, LPARAM lparam) -> BOOL { + return (*(function*)lparam)(hwnd); + }, (LPARAM)&callbackFunc); + } + + BOOL EnumChildWindowsHelper(HWND hwndParent, _In_ const function& callbackFunc) + { + return EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lparam) -> BOOL { + return (*(function*)lparam)(hwnd); + }, (LPARAM)&callbackFunc); + } + + bool IsWindowClass(_In_ const HWND hwnd, _In_ LPCWSTR pszWindowClass) + { + if (hwnd && pszWindowClass && *pszWindowClass) + { + const int BUFFER_SIZE = 100; + WCHAR szClassname[BUFFER_SIZE]; + + int result = ::GetClassName(hwnd, (LPWSTR)&szClassname, BUFFER_SIZE); + if (result && _wcsicmp(szClassname, pszWindowClass) == 0) + { + return true; + } + } + + return false; + } + + HRESULT GetDocumentFromSite(_In_ IUnknown* spSite, _Out_ CComPtr& spDocumentOut) + { + ATLENSURE_RETURN_HR(spSite != nullptr, E_INVALIDARG); + + CComQIPtr spDocument(spSite); + if (spDocument.p != nullptr) + { + CComQIPtr spDocDisp(spDocument); + ATLENSURE_RETURN_HR(spDocDisp.p != nullptr, E_NOINTERFACE); + + spDocumentOut = spDocDisp; + return S_OK; + } + else + { + CComQIPtr spBrowser2(spSite); + if (spBrowser2 != nullptr) + { + CComPtr spDocDisp; + HRESULT hr = spBrowser2->get_Document(&spDocDisp); + FAIL_IF_NOT_S_OK(hr); + ATLENSURE_RETURN_HR(spDocDisp.p != nullptr, E_FAIL); + + spDocumentOut = spDocDisp; + return hr; + } + else + { + CComQIPtr webAppHost(spSite); + if (webAppHost.p != nullptr) + { + CComPtr spDoc; + HRESULT hr = webAppHost->get_Document(&spDoc); + FAIL_IF_NOT_S_OK(hr); + CComQIPtr spDocDisp(spDoc); + ATLENSURE_RETURN_HR(spDocDisp.p != nullptr, E_NOINTERFACE); + + spDocumentOut = spDocDisp; + return S_OK; + } + } + } + + return E_NOINTERFACE; + } + + HRESULT GetDocumentFromHwnd(_In_ const HWND browserHwnd, _Out_ CComPtr& spDocument) + { + DWORD_PTR messageResult = 0; + LRESULT sendMessageResult = ::SendMessageTimeoutW(browserHwnd, Helpers::GetHtmlDocumentMessage(), 0L, 0L, SMTO_ABORTIFHUNG, 2000, &messageResult); + + if (sendMessageResult != 0 && messageResult != 0) + { + HMODULE* hModule = new HMODULE(::LoadLibraryEx(L"oleacc.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); + shared_ptr spOleAcc(hModule, [](HMODULE* hModule) { ::FreeLibrary(*hModule); delete hModule; }); + if (spOleAcc) + { + auto pfObjectFromLresult = reinterpret_cast(::GetProcAddress(*spOleAcc.get(), "ObjectFromLresult")); + if (pfObjectFromLresult != nullptr) + { + return pfObjectFromLresult(messageResult, IID_IHTMLDocument2, 0, reinterpret_cast(&spDocument.p)); + } + } + } + + return E_FAIL; + } + + UINT GetHtmlDocumentMessage() + { + if (s_getHtmlDocumentMessage == 0) + { + s_getHtmlDocumentMessage = ::RegisterWindowMessageW(L"WM_HTML_GETOBJECT"); + } + + return s_getHtmlDocumentMessage; + } + + CString FormatErrorMessage(_In_ DWORD ErrorCode) + { + TCHAR *pMsgBuf = NULL; + DWORD nMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&pMsgBuf), 0, NULL); + if (!nMsgLen) + { + return _T("FormatMessage fail"); + } + CString sMsg(pMsgBuf, nMsgLen); + LocalFree(pMsgBuf); + return sMsg; + } +} diff --git a/src/Helpers.h b/src/Helpers.h new file mode 100644 index 0000000..1cd4040 --- /dev/null +++ b/src/Helpers.h @@ -0,0 +1,27 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// + +#pragma once +#include +#include + +// Define some assert helper macros +#define FAIL_IF_NOT_S_OK(hr) ATLENSURE_RETURN_HR(hr == S_OK, SUCCEEDED(hr) ? E_FAIL : hr) +#define THROW_IF_NOT_S_OK(hr) ATLENSURE_THROW(hr == S_OK, SUCCEEDED(hr) ? E_FAIL : hr) +#define FAIL_IF_ERROR(jec) ATLENSURE_RETURN_HR(jec == JsNoError, jec) +#define THROW_IF_ERROR(jec) ATLENSURE_THROW(jec == JsNoError, jec) + +namespace Helpers +{ + BOOL EnumWindowsHelper(_In_ const function& callbackFunc); + BOOL EnumChildWindowsHelper(HWND hwndParent, _In_ const function& callbackFunc); + bool IsWindowClass(_In_ const HWND hwnd, _In_ LPCWSTR pszWindowClass); + + HRESULT GetDocumentFromSite(_In_ IUnknown* spSite, _Out_ CComPtr& spDocumentOut); + HRESULT GetDocumentFromHwnd(_In_ const HWND browserHwnd, _Out_ CComPtr& spDocument); + UINT GetHtmlDocumentMessage(); + static UINT s_getHtmlDocumentMessage; + + CString FormatErrorMessage(_In_ DWORD ErrorCode); +} diff --git a/src/MicrosoftEdgeLauncher.cpp b/src/MicrosoftEdgeLauncher.cpp index 940b117..006010c 100644 --- a/src/MicrosoftEdgeLauncher.cpp +++ b/src/MicrosoftEdgeLauncher.cpp @@ -2,58 +2,292 @@ #include "MicrosoftEdgeLauncher.h" #include #include +#include +#include "Helpers.h" +#include int _tmain(int argc, _TCHAR* argv[]) { - HRESULT hr = E_FAIL; + HRESULT hr = S_OK; hr = CoInitialize(NULL); + + PCWSTR pszUrl = L""; + bool bKeepAlive = false; + if (!SUCCEEDED(hr)) - { + { return hr; } - if (2 == argc) - { - if ((argv[1] == L"-h") | (argv[1] == L"-help")) - { - std::cout << "Launches the Microsoft Edge browser."; - std::cout << "\n"; - std::cout << "\n"; - std::cout << "Usage:"; - std::cout << "\n"; - std::cout << "\tMicrosoftEdgeLauncher.exe [url]"; - std::cout << "\n"; - std::cout << "\n"; - std::cout << "\turl - The URL to open in Microsoft Edge."; - std::cout << "\n"; - std::cout << "\n"; - hr = E_ABORT; - } - else - { - hr = OpenUrlInMicrosoftEdge(argv[1]); - } + if (argc == 2) + { + if ((wcscmp(argv[1], L"-?") == 0) | (wcscmp(argv[1], L"-h") == 0) | (wcscmp(argv[1], L"--help") == 0)) + { + ShowHelp(); + hr = E_ABORT; + } + else + { + pszUrl = argv[1]; + } + } + else if (argc == 3) + { + pszUrl = argv[1]; + if ((wcscmp(argv[2], L"-k") == 0) | (wcscmp(argv[2], L"--keepalive") == 0)) + { + bKeepAlive = true; + } + else + { + std::cout << "Error: Expecting -k got: \n " << argv[2]; + std::cout << "\n"; + std::cout << "\nUse -h for usage info."; + hr = E_ABORT; + } } else - { - hr = OpenUrlInMicrosoftEdge(L""); + { + pszUrl = L"https://www.bing.com/"; } + + hr = LaunchEdge(pszUrl, bKeepAlive); + CoUninitialize(); return hr; } -HRESULT OpenUrlInMicrosoftEdge(__in PCWSTR url) -{ - HRESULT hr = E_FAIL; - CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - SHELLEXECUTEINFOW sei = { sizeof sei }; - sei.lpVerb = L"open"; - std::wstring mywstring(url); - std::wstring concatted_stdstr = L"microsoft-edge:" + mywstring; - sei.lpFile = concatted_stdstr.c_str(); - hr = ShellExecuteExW(&sei); - if (FAILED(hr)) - { - std::cout << L"Failed to launch Microsoft Edge"; - } - return hr; + +HRESULT LaunchEdge(_In_ PCWSTR pszUrl, _In_ BOOL bKeepAlive) +{ + if(!bKeepAlive) + { + return LaunchEdgeViaShellExec(pszUrl); + } + + HRESULT hr; + DWORD dwProcessID; + + std::srand(std::time(0)); + + int iRandomSeed = rand() % 1000 + 1; + wstringstream ss; + ss << L"http://example.com/?" << iRandomSeed; + wstring wsInitialUrl = ss.str(); + PCWSTR pszInitialUrl = wsInitialUrl.c_str(); + + hr = LaunchEdgeViaShellExec(pszInitialUrl); + if (!SUCCEEDED(hr)) + { + return hr; + } + + EdgeTargetInfo info; + info = WatchForEdgeTab(pszInitialUrl); + if (info.pid == 0) + { + return E_NOINTERFACE; + } + + if (info.spDoc == nullptr) + { + return E_NOINTERFACE; + } + + CComPtr spWindow; + hr = info.spDoc->get_parentWindow(&spWindow); + if (!SUCCEEDED(hr)) + { + ShowLastError(L"Failed to get the IHTMLWindow2"); + return hr; + } + + spWindow->navigate(SysAllocString(pszUrl)); + if (!SUCCEEDED(hr)) + { + ShowLastError(L"Failed to navigate to launch URL"); + return hr; + } + + hr = WaitForProcessToExit(info.pid); + return hr; +} + +HRESULT LaunchEdgeViaShellExec(PCWSTR pszUrl) +{ + HRESULT hr; + SHELLEXECUTEINFOW sei = { sizeof sei }; + sei.lpVerb = L"open"; + std::wstring wsUrl(pszUrl); + std::wstring concatted_stdstr = L"microsoft-edge:" + wsUrl; + sei.lpFile = concatted_stdstr.c_str(); + hr = ShellExecuteExW(&sei); + + if (!SUCCEEDED(hr)) + { + ShowLastError(L"Failed to launch Microsoft Edge via shell execute"); + return hr; + } + + return hr; +} + +void ShowHelp() +{ + std::cout << "Launches the Microsoft Edge browser."; + std::cout << "\n"; + std::cout << "\n"; + std::cout << "Usage:"; + std::cout << "\n"; + std::cout << "\tMicrosoftEdgeLauncher.exe [url] -k"; + std::cout << "\n"; + std::cout << "\nurl - The URL to open in Microsoft Edge."; + std::cout << "\n-k - Keep this program alive for as long as the launched process is alive."; + std::cout << "\n"; +} + +HRESULT ShowLastError(_In_ PCWSTR pszErrorIntro) +{ + DWORD dwError = GetLastError(); + + CString strErorrMessage = Helpers::FormatErrorMessage(dwError); + + std::cout << "\nError "; + std::cout << "\n"; + std::cout << "\n" << pszErrorIntro; + std::cout << "\n"; + std::cout << "\nError code: " << dwError; + std::cout << "\nError message:"; + std::cout << "\n" << strErorrMessage; + std::cout << "\n"; + + return HRESULT_FROM_WIN32(dwError); +} + + +HRESULT WaitForProcessToExit(_In_ DWORD dwPid) +{ + HRESULT hr = S_OK; + DWORD dwResult; + HANDLE hndProcess; + + hndProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); + if (hndProcess == NULL) + { + return ShowLastError(L"Error opening process"); + } + + std::cout << "\nWaiting for process " << dwPid <<" to exit..."; + dwResult = WaitForSingleObject(hndProcess, LifetimeTimeoutMs); + + switch (dwResult) + { + case WAIT_OBJECT_0: + return S_OK; + break; + case WAIT_TIMEOUT: + std::cout << "\nTimed out waiting for process to exit."; + return E_FAIL; + break; + default: + return ShowLastError(L"Error wating for process exit."); + break; + } + + return hr; +} + +EdgeTargetInfo WatchForEdgeTab(_In_ PCWSTR pszUrl) +{ + EdgeTargetInfo info = { 0 }; + int loopCounter = 0; + int waitTimeMS = 1000; + int maxTries = 20; + + do + { + Sleep(waitTimeMS); + loopCounter++; + vector vTargets; + + EnumerateTargets(vTargets); + + for (size_t i = 0; i < vTargets.size(); i++) + { + EdgeTargetInfo info = vTargets[i]; + if ((wcscmp(info.url, pszUrl) == 0)) { + return info; + } + } + + if (loopCounter > maxTries) + { + std::cout << "\nCouldn't find Edge URL with URL: " << pszUrl; + std::cout << "\nFound"; + + for (size_t i = 0; i < vTargets.size(); i++) + { + EdgeTargetInfo info = vTargets[i]; + std::cout << "\n" << info.url; + } + + return{ 0 }; + } + } while (info.pid == 0); + + return info; +} + +HRESULT EnumerateTargets(vector& vTargets) +{ + vTargets.empty(); + Helpers::EnumWindowsHelper([&](HWND hwndTop) -> BOOL + { + Helpers::EnumChildWindowsHelper(hwndTop, [&](HWND hwnd) -> BOOL + { + if (Helpers::IsWindowClass(hwnd, L"Internet Explorer_Server")) + { + bool isEdgeContentProcess = false; + + DWORD processId; + ::GetWindowThreadProcessId(hwnd, &processId); + + CString processName; + CHandle handle(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId)); + if (handle) + { + DWORD length = ::GetModuleFileNameEx(handle, nullptr, processName.GetBufferSetLength(MAX_PATH), MAX_PATH); + processName.ReleaseBuffer(length); + isEdgeContentProcess = (processName.Find(L"MicrosoftEdgeCP.exe") == processName.GetLength() - 19); + + processName = ::PathFindFileNameW(processName); + } + + if (isEdgeContentProcess) + { + CComPtr spDocument; + HRESULT hr = Helpers::GetDocumentFromHwnd(hwnd, spDocument); + if (hr == S_OK) + { + EdgeTargetInfo i; + i.hwnd = hwnd; + i.pid = processId; + i.spDoc = spDocument; + + hr = spDocument->get_URL(&i.url); + if (hr != S_OK) + { + i.url = L"unknown"; + } + vTargets.push_back(i); + } + } + } + + return TRUE; + }); + + return TRUE; + }); + + return S_OK; } diff --git a/src/MicrosoftEdgeLauncher.h b/src/MicrosoftEdgeLauncher.h index eb10a96..258751a 100644 --- a/src/MicrosoftEdgeLauncher.h +++ b/src/MicrosoftEdgeLauncher.h @@ -1,4 +1,20 @@ #include "stdafx.h" #include +#include -HRESULT OpenUrlInMicrosoftEdge(__in PCWSTR url); \ No newline at end of file +struct EdgeTargetInfo +{ + HWND hwnd; + BSTR url; + DWORD pid; + CComPtr spDoc; +}; + +HRESULT LaunchEdge(_In_ PCWSTR pszUrl, _In_ BOOL bKeepAlive); +HRESULT LaunchEdgeViaShellExec(_In_ PCWSTR pszUrl); +EdgeTargetInfo WatchForEdgeTab(_In_ PCWSTR pszUrl); +HRESULT EnumerateTargets(_Out_ std::vector& vTargets); +HRESULT WaitForProcessToExit(_In_ DWORD dwPid); +void ShowHelp(); +HRESULT ShowLastError(_In_ PCWSTR pszErrorIntro); +int LifetimeTimeoutMs = INFINITE; diff --git a/src/MicrosoftEdgeLauncher.vcxproj b/src/MicrosoftEdgeLauncher.vcxproj index c3fbd6d..7c2e917 100644 --- a/src/MicrosoftEdgeLauncher.vcxproj +++ b/src/MicrosoftEdgeLauncher.vcxproj @@ -79,12 +79,14 @@ + + Create diff --git a/src/MicrosoftEdgeLauncher.vcxproj.filters b/src/MicrosoftEdgeLauncher.vcxproj.filters index 040205a..34526c4 100644 --- a/src/MicrosoftEdgeLauncher.vcxproj.filters +++ b/src/MicrosoftEdgeLauncher.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + @@ -35,6 +38,9 @@ Source Files + + Source Files + diff --git a/src/stdafx.h b/src/stdafx.h index 36b1da7..910fc92 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -14,4 +14,11 @@ #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit #include -#include \ No newline at end of file +#include +#include +#include +#include +#include +#include + +using namespace std;