├── .gitattributes ├── .gitignore ├── CmdLineParser.cpp ├── CmdLineParser.h ├── ConsoleRedir.cpp ├── InteractiveSession.cpp ├── PAExec.cpp ├── PAExec.h ├── PAExec.rc ├── PAExec.sln ├── PAExec.vcproj ├── PAExec.vcxproj ├── PAExec.vcxproj.filters ├── Parsing.cpp ├── Process.cpp ├── README.md ├── Remote.cpp ├── ServiceImpl.cpp ├── Usage.txt ├── UtilityFuncs.cpp ├── regression1.txt ├── regression2.txt ├── resource.h ├── stdafx.cpp ├── stdafx.h └── targetver.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | .vs/PAExec/v15/Browse.VC.db 217 | .vs/PAExec/v15/Browse.VC.opendb 218 | -------------------------------------------------------------------------------- /CmdLineParser.cpp: -------------------------------------------------------------------------------- 1 | // CmdLineParser.cpp: implementation of the CCmdLineParser class. 2 | // 3 | // Copyright (c) Pavel Antonov, 2002 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | #include "stdafx.h" 11 | #include "CmdLineParser.h" 12 | 13 | const TCHAR CCmdLineParser::m_sDelimeters[] = _T("-/"); //GLOK 14 | const TCHAR CCmdLineParser::m_sQuotes[] = _T("\""); // Can be _T("\"\'"), for instance //GLOK 15 | const TCHAR CCmdLineParser::m_sValueSep[] = _T(" :="); // Space MUST be in set //GLOK 16 | 17 | ////////////////////////////////////////////////////////////////////// 18 | // Construction/Destruction 19 | ////////////////////////////////////////////////////////////////////// 20 | 21 | CCmdLineParser::CCmdLineParser(LPCTSTR sCmdLine, bool bCaseSensitive) 22 | : m_bCaseSensitive(bCaseSensitive) 23 | { 24 | if(sCmdLine) { 25 | Parse(sCmdLine); 26 | } 27 | } 28 | 29 | CCmdLineParser::~CCmdLineParser() 30 | { 31 | m_ValsMap.clear(); 32 | } 33 | 34 | bool CCmdLineParser::Parse(LPCTSTR sCmdLine) { 35 | if(!sCmdLine) return false; 36 | 37 | m_sCmdLine = sCmdLine; 38 | m_ValsMap.clear(); 39 | const CCmdLineParser_String sEmpty; 40 | 41 | int nArgs = 0; 42 | LPCTSTR sCurrent = sCmdLine; 43 | while(true) 44 | { 45 | // /Key:"arg" 46 | if(_tcslen(sCurrent) == 0) 47 | break; // No data left 48 | 49 | LPCTSTR sArg = _tcspbrk(sCurrent, m_sDelimeters); 50 | 51 | if(!sArg) 52 | break; // No delimiters found 53 | 54 | sArg = _tcsinc(sArg); 55 | 56 | // Key:"arg" 57 | if(_tcslen(sArg) == 0) 58 | break; // String ends with delimeter 59 | 60 | LPCTSTR sVal = _tcspbrk(sArg, m_sValueSep); 61 | if(sVal == NULL) 62 | { //Key ends command line 63 | CCmdLineParser_String csKey(sArg); 64 | if(!m_bCaseSensitive) 65 | csKey.MakeLower(); 66 | 67 | m_ValsMap.insert(CValsMap::value_type(csKey, sEmpty)); 68 | break; 69 | } 70 | 71 | //if here, we're on one of: 72 | //white space 73 | //real separator 74 | bool bNoVal = false; 75 | if(sVal[0] == ' ') 76 | { 77 | //the next non-white space char is either a value or next delimiter 78 | LPCWSTR scanForward = sVal; 79 | while(_istspace(*scanForward)) 80 | scanForward++; 81 | if((NULL != _tcschr(m_sDelimeters, *scanForward)) || (*scanForward == L'\0')) 82 | { 83 | //on a delimiter (or end of string), so sVal is not going to be a value (ie this arg has no value) 84 | bNoVal = true; 85 | } 86 | else 87 | { 88 | //sVal is a real value 89 | } 90 | } 91 | 92 | if(bNoVal) 93 | { // Key with no value or cmdline ends with /Key: 94 | CCmdLineParser_String csKey(sArg, (int)(sVal - sArg)); 95 | if(!csKey.IsEmpty()) 96 | { // Prevent /: case 97 | if(!m_bCaseSensitive) 98 | { 99 | csKey.MakeLower(); 100 | } 101 | m_ValsMap.insert(CValsMap::value_type(csKey, sEmpty)); 102 | } 103 | sCurrent = _tcsinc(sVal); 104 | continue; 105 | } 106 | else 107 | { // Key with value 108 | CCmdLineParser_String csKey(sArg, (int)(sVal - sArg)); 109 | if(!m_bCaseSensitive) 110 | { 111 | csKey.MakeLower(); 112 | } 113 | 114 | sVal = _tcsinc(sVal); 115 | // "arg" 116 | LPCTSTR sQuote = _tcspbrk(sVal, m_sQuotes), sEndQuote(NULL); 117 | if(sQuote == sVal) 118 | { // Quoted String 119 | sQuote = _tcsinc(sVal); 120 | sEndQuote = _tcspbrk(sQuote, m_sQuotes); 121 | } 122 | else 123 | { 124 | sQuote = sVal; 125 | sEndQuote = _tcschr(sQuote, _T(' ')); 126 | } 127 | 128 | if(sEndQuote == NULL) 129 | { // No end quotes or terminating space, take rest of string 130 | CCmdLineParser_String csVal(sQuote); 131 | if(!csKey.IsEmpty()) 132 | { // Prevent /:val case 133 | m_ValsMap.insert(CValsMap::value_type(csKey, csVal)); 134 | } 135 | break; 136 | } 137 | else 138 | { // End quote or space present 139 | if(!csKey.IsEmpty()) 140 | { // Prevent /:"val" case 141 | CCmdLineParser_String csVal(sQuote, (int)(sEndQuote - sQuote)); 142 | m_ValsMap.insert(CValsMap::value_type(csKey, csVal)); 143 | } 144 | sCurrent = _tcsinc(sEndQuote); 145 | continue; 146 | } 147 | } 148 | } 149 | 150 | return (nArgs > 0); 151 | } 152 | 153 | CCmdLineParser::CValsMap::const_iterator CCmdLineParser::findKey(LPCTSTR sKey) const { 154 | CCmdLineParser_String s(sKey); 155 | if(!m_bCaseSensitive) { 156 | s.MakeLower(); 157 | } 158 | return m_ValsMap.find(s); 159 | } 160 | 161 | void CCmdLineParser::SetVal(LPCTSTR sKey, LPCTSTR val) 162 | { 163 | CCmdLineParser_String key = sKey; 164 | if(!m_bCaseSensitive) 165 | key.MakeLower(); 166 | m_ValsMap.insert(CValsMap::value_type(key, val)); 167 | } 168 | 169 | // TRUE if "Key" present in command line 170 | bool CCmdLineParser::HasKey(LPCTSTR sKey) const { 171 | CValsMap::const_iterator it = findKey(sKey); 172 | if(it == m_ValsMap.end()) return false; 173 | return true; 174 | } 175 | 176 | // Is "key" present in command line and have some value 177 | bool CCmdLineParser::HasVal(LPCTSTR sKey) const { 178 | CValsMap::const_iterator it = findKey(sKey); 179 | if(it == m_ValsMap.end()) return false; 180 | if(it->second.IsEmpty()) return false; 181 | return true; 182 | } 183 | // Returns value if value was found or NULL otherwise 184 | LPCTSTR CCmdLineParser::GetVal(LPCTSTR sKey) const { 185 | CValsMap::const_iterator it = findKey(sKey); 186 | if(it == m_ValsMap.end()) return false; 187 | return LPCTSTR(it->second); 188 | } 189 | // Returns true if value was found 190 | bool CCmdLineParser::GetVal(LPCTSTR sKey, CCmdLineParser_String& sValue) const { 191 | CValsMap::const_iterator it = findKey(sKey); 192 | if(it == m_ValsMap.end()) return false; 193 | sValue = it->second; 194 | return true; 195 | } 196 | 197 | CCmdLineParser::POSITION CCmdLineParser::getFirst() const { 198 | return m_ValsMap.begin(); 199 | } 200 | CCmdLineParser::POSITION CCmdLineParser::getNext(POSITION& pos, CCmdLineParser_String& sKey, CCmdLineParser_String& sValue) const { 201 | if(isLast(pos)) { 202 | sKey.Empty(); 203 | return pos; 204 | } else { 205 | sKey = pos->first; 206 | sValue = pos->second; 207 | pos ++; 208 | return pos; 209 | } 210 | } 211 | // just helper ;) 212 | bool CCmdLineParser::isLast(POSITION& pos) const { 213 | return (pos == m_ValsMap.end()); 214 | } 215 | -------------------------------------------------------------------------------- /CmdLineParser.h: -------------------------------------------------------------------------------- 1 | // CmdLineParser.h: interface for the CCmdLineParser class. 2 | // 3 | // Copyright (c) Pavel Antonov, 2002 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | #if !defined(AFX_CMDLINEPARSER_H__BE51B7B0_4BC9_44F1_9B88_DF33BE4280DF__INCLUDED_) 11 | #define AFX_CMDLINEPARSER_H__BE51B7B0_4BC9_44F1_9B88_DF33BE4280DF__INCLUDED_ 12 | 13 | #if _MSC_VER > 1000 14 | #pragma once 15 | #endif // _MSC_VER > 1000 16 | 17 | typedef CString CCmdLineParser_String ; 18 | 19 | #include 20 | 21 | // Example of strings 22 | // /Key1 /Key2 -Key3:Val3 -Key4:"Val 4-with/spaces/and-delimeters" /Key5:Val5 23 | // /Key:"" is equal to /Key: and is equal to /Key 24 | // /Key is equal to -Key 25 | // If getCaseSensitive is false (by default), all keys are made lowercase. 26 | 27 | // Examples of use: 28 | // CCmdLineParser parser(_T("/Key1 /Key2: -Key3:Val3 -Key4:\"Val 4-with/spaces/and-delimeters\" /Key5:Val5")); 29 | // ASSERT(parser.HasKey("Key1") == true); 30 | // ASSERT(parser.HasKey("Key10") == false); 31 | // ASSERT(parser.HasVal("Key2") == false); 32 | // parser.GetVal("Key1") -> []; (empty string) 33 | // parser.GetVal("Key2") -> []; (empty string) 34 | // parser.GetVal("Key3") -> [Val3]; 35 | // parser.GetVal("Key4") -> [Val 4-with/spaces/and-delimeters]; 36 | // CCmdLineParser::POSITION pos = parser.getFirst(); 37 | // CString sKey, sVal; 38 | // while(!parser.isLast(pos)) { 39 | // parser.getNext(pos, sKey, sVal); 40 | // printf("Key: [%s], Val: [%s]"); 41 | // } 42 | 43 | 44 | class CCmdLineParser { 45 | public: 46 | class CValsMap : public std::map {}; 47 | typedef CValsMap::const_iterator POSITION; 48 | public: 49 | CCmdLineParser(LPCTSTR sCmdLine = NULL, bool bCaseSensitive = false); 50 | virtual ~CCmdLineParser(); 51 | 52 | bool Parse(LPCTSTR sCmdLine); 53 | 54 | LPCTSTR getCmdLine() const { return m_sCmdLine; } 55 | 56 | void setCaseSensitive(bool bSensitive) { m_bCaseSensitive = bSensitive; } 57 | bool getCaseSensitive() const { return m_bCaseSensitive; } 58 | 59 | const CValsMap& getVals() const { return m_ValsMap; } 60 | 61 | // Start iterating through keys and values 62 | POSITION getFirst() const; 63 | // Get next key-value pair, returns empty sKey if end reached 64 | POSITION getNext(POSITION& pos, CCmdLineParser_String& sKey, CCmdLineParser_String& sValue) const; 65 | // just helper ;) 66 | bool isLast(POSITION& pos) const; 67 | 68 | // TRUE if "Key" present in command line 69 | bool HasKey(LPCTSTR sKey) const; 70 | // Is "key" present in command line and have some value 71 | bool HasVal(LPCTSTR sKey) const; 72 | // Returns value if value was found or NULL otherwise 73 | LPCTSTR GetVal(LPCTSTR sKey) const; 74 | // Returns true if value was found 75 | bool GetVal(LPCTSTR sKey, CCmdLineParser_String& sValue) const; 76 | 77 | void SetVal(LPCTSTR sKey, LPCTSTR val); 78 | 79 | private: 80 | CValsMap::const_iterator findKey(LPCTSTR sKey) const; 81 | private: 82 | CCmdLineParser_String m_sCmdLine; 83 | CValsMap m_ValsMap; 84 | bool m_bCaseSensitive; 85 | 86 | static const TCHAR m_sDelimeters[]; 87 | static const TCHAR m_sValueSep[]; 88 | static const TCHAR m_sQuotes[]; 89 | }; 90 | 91 | #endif // !defined(AFX_CMDLINEPARSER_H__BE51B7B0_4BC9_44F1_9B88_DF33BE4280DF__INCLUDED_) 92 | -------------------------------------------------------------------------------- /ConsoleRedir.cpp: -------------------------------------------------------------------------------- 1 | // ConsoleRedir.cpp: Redirection of Console I/O 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | #include "stdafx.h" 11 | #include "PAExec.h" 12 | #include 13 | #include 14 | 15 | const DWORD gPIPE_TYPE = PIPE_TYPE_BYTE | PIPE_WAIT; //PIPE_TYPE_MESSAGE | PIPE_WAIT; 16 | 17 | #ifdef _DEBUG 18 | #define new DEBUG_NEW 19 | #undef THIS_FILE 20 | static char THIS_FILE[] = __FILE__; 21 | #endif 22 | 23 | 24 | // Creates named pipes for stdout, stderr, stdin on the (remote) service side 25 | bool CreateIOPipesInService(Settings& settings, LPCWSTR caller, DWORD pid) 26 | { 27 | SECURITY_DESCRIPTOR SecDesc; 28 | InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION); 29 | SetSecurityDescriptorDacl(&SecDesc, TRUE, NULL, FALSE); 30 | 31 | SECURITY_ATTRIBUTES SecAttrib = {0}; 32 | SecAttrib.nLength = sizeof(SECURITY_ATTRIBUTES); 33 | SecAttrib.lpSecurityDescriptor = &SecDesc;; 34 | SecAttrib.bInheritHandle = TRUE; 35 | 36 | DWORD gle = 0; 37 | CString pipeName; 38 | 39 | // Create StdOut pipe 40 | pipeName = StrFormat(L"\\\\.\\pipe\\PAExecOut%s%u", caller, pid); 41 | settings.hStdOut = CreateNamedPipe(pipeName, 42 | PIPE_ACCESS_OUTBOUND, 43 | gPIPE_TYPE, 44 | PIPE_UNLIMITED_INSTANCES, 45 | 0, 46 | 0, 47 | (DWORD)-1, 48 | &SecAttrib); 49 | gle = GetLastError(); 50 | if(BAD_HANDLE(settings.hStdOut)) 51 | Log(StrFormat(L"PAExec failed to create pipe %s.", pipeName), gle); 52 | 53 | // Create StdError pipe 54 | pipeName = StrFormat(L"\\\\.\\pipe\\PAExecErr%s%u", caller, pid); 55 | settings.hStdErr = CreateNamedPipe(pipeName, 56 | PIPE_ACCESS_OUTBOUND, 57 | gPIPE_TYPE, 58 | PIPE_UNLIMITED_INSTANCES, 59 | 0, 60 | 0, 61 | (DWORD)-1, 62 | &SecAttrib); 63 | gle = GetLastError(); 64 | if(BAD_HANDLE(settings.hStdErr)) 65 | Log(StrFormat(L"PAExec failed to create pipe %s.", pipeName), gle); 66 | 67 | // Create StdIn pipe 68 | pipeName = StrFormat(L"\\\\.\\pipe\\PAExecIn%s%u", caller, pid); 69 | settings.hStdIn = CreateNamedPipe(pipeName, 70 | PIPE_ACCESS_INBOUND, 71 | gPIPE_TYPE, 72 | PIPE_UNLIMITED_INSTANCES, 73 | 0, 74 | 0, 75 | (DWORD)-1, 76 | &SecAttrib); 77 | gle = GetLastError(); 78 | if(BAD_HANDLE(settings.hStdIn)) 79 | Log(StrFormat(L"PAExec failed to create pipe %s.", pipeName), gle); 80 | 81 | if (BAD_HANDLE(settings.hStdOut) || 82 | BAD_HANDLE(settings.hStdErr) || 83 | BAD_HANDLE(settings.hStdIn)) 84 | { 85 | CloseHandle(settings.hStdOut); 86 | CloseHandle(settings.hStdErr); 87 | CloseHandle(settings.hStdIn); 88 | settings.hStdOut = NULL; 89 | settings.hStdErr = NULL; 90 | settings.hStdIn = NULL; 91 | 92 | Log(L"Error creating redirection pipes", true); 93 | return false; 94 | } 95 | 96 | // Waiting for client to connect to this pipe 97 | ConnectNamedPipe(settings.hStdOut, NULL ); 98 | ConnectNamedPipe(settings.hStdErr, NULL ); 99 | ConnectNamedPipe(settings.hStdIn, NULL ); 100 | 101 | Log(L"DEBUG: Client connected to pipe", false); 102 | 103 | return true; 104 | } 105 | 106 | #define SIZEOF_BUFFER 256 //don't go much higher than 30000 or ERROR_NOT_ENOUGH_MEMORY is returned from ReadConsole 107 | 108 | // Listens the remote stdout pipe 109 | // Remote process will send its stdout to this pipe 110 | UINT WINAPI ListenRemoteOutPipeThread(void* p) 111 | { 112 | ListenParam* pLP = (ListenParam*)p; 113 | 114 | HANDLE hOutput = GetStdHandle( STD_OUTPUT_HANDLE ); 115 | char szBuffer[SIZEOF_BUFFER] = {0}; 116 | DWORD dwRead = 0; 117 | 118 | HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 119 | 120 | while(false == gbStop) 121 | { 122 | OVERLAPPED olR = {0}; 123 | olR.hEvent = hEvent; 124 | if (!ReadFile( pLP->pSettings->hStdOut, szBuffer, SIZEOF_BUFFER - 1, &dwRead, &olR) || (dwRead == 0)) 125 | { 126 | DWORD dwErr = GetLastError(); 127 | if ( dwErr == ERROR_NO_DATA) 128 | break; 129 | } 130 | 131 | if(gbStop) 132 | break; 133 | 134 | HANDLE waits[2]; 135 | waits[0] = pLP->hStop; 136 | waits[1] = hEvent; 137 | DWORD ret = WaitForMultipleObjects(2, waits, FALSE, INFINITE); 138 | if(ret == WAIT_OBJECT_0) 139 | break; //need to exit 140 | _ASSERT(ret == WAIT_OBJECT_0 + 1); //data in buffer now 141 | 142 | // Handle CLS command, just for fun :) 143 | switch( szBuffer[0] ) 144 | { 145 | case 12: //cls 146 | { 147 | DWORD dwWritten = 0; 148 | COORD origin = {0,0}; 149 | CONSOLE_SCREEN_BUFFER_INFO sbi = {0}; 150 | 151 | if ( GetConsoleScreenBufferInfo( hOutput, &sbi ) ) 152 | { 153 | FillConsoleOutputCharacter( 154 | hOutput, 155 | _T(' '), 156 | sbi.dwSize.X * sbi.dwSize.Y, 157 | origin, 158 | &dwWritten ); 159 | 160 | SetConsoleCursorPosition( 161 | hOutput, 162 | origin ); 163 | } 164 | } 165 | continue; 166 | break; 167 | } 168 | 169 | szBuffer[ dwRead / sizeof(szBuffer[0]) ] = _T('\0'); 170 | 171 | if(gbStop) 172 | break; 173 | 174 | //before printing, see if this is output that should be suppressed 175 | EnterCriticalSection(&pLP->cs); 176 | bool bSuppress = false; 177 | for(std::vector::iterator itr = pLP->inputSentToSuppressInOutput.begin(); pLP->inputSentToSuppressInOutput.end() != itr; itr++) 178 | { 179 | if(0 == strcmp(szBuffer, (*itr).c_str())) 180 | { 181 | bSuppress = true; 182 | pLP->inputSentToSuppressInOutput.erase(itr); 183 | break; 184 | } 185 | } 186 | LeaveCriticalSection(&pLP->cs); 187 | 188 | if(false == bSuppress) 189 | { 190 | // Send it to our stdout 191 | fprintf(stdout, "%s", szBuffer); 192 | fflush(stdout); 193 | } 194 | } 195 | 196 | CloseHandle(hEvent); 197 | 198 | InterlockedDecrement(&pLP->workerThreads); 199 | 200 | return 0; 201 | } 202 | 203 | // Listens the remote stderr pipe 204 | // Remote process will send its stderr to this pipe 205 | UINT WINAPI ListenRemoteErrorPipeThread(void* p) 206 | { 207 | //giThreadsWorking already incremented 208 | ListenParam* pLP = (ListenParam*)p; 209 | 210 | char szBuffer[SIZEOF_BUFFER]; 211 | DWORD dwRead; 212 | 213 | HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 214 | 215 | while(false == gbStop) 216 | { 217 | OVERLAPPED olR = {0}; 218 | olR.hEvent = hEvent; 219 | if(!ReadFile( pLP->pSettings->hStdErr, szBuffer, SIZEOF_BUFFER - 1, &dwRead, &olR) || (dwRead == 0)) 220 | { 221 | DWORD dwErr = GetLastError(); 222 | if ( dwErr == ERROR_NO_DATA) 223 | break; 224 | } 225 | 226 | if(gbStop) 227 | break; 228 | 229 | HANDLE waits[2]; 230 | waits[0] = pLP->hStop; 231 | waits[1] = olR.hEvent; 232 | DWORD ret = WaitForMultipleObjects(2, waits, FALSE, INFINITE); 233 | if(ret == WAIT_OBJECT_0) 234 | break; //need to exit 235 | _ASSERT(ret == WAIT_OBJECT_0 + 1); //data in buffer now 236 | 237 | szBuffer[ dwRead / sizeof(szBuffer[0]) ] = _T('\0'); 238 | 239 | // Write it to our stderr 240 | fprintf(stderr, "%s", szBuffer); 241 | fflush(stderr); 242 | } 243 | 244 | CloseHandle(hEvent); 245 | 246 | InterlockedDecrement(&pLP->workerThreads); 247 | 248 | return 0; 249 | } 250 | 251 | // Listens our console, and if the user types in something, 252 | // we will pass it to the remote machine. 253 | // ReadConsole return after pressing the ENTER 254 | UINT WINAPI ListenRemoteStdInputPipeThread(void* p) 255 | { 256 | //giThreadsWorking already incremented 257 | ListenParam* pLP = (ListenParam*)p; 258 | 259 | HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); 260 | char szInputBuffer[SIZEOF_BUFFER] = {0}; 261 | DWORD nBytesRead = 0; 262 | DWORD nBytesWrote = 0; 263 | 264 | HANDLE hWritePipe = CreateEvent(NULL, TRUE, FALSE, NULL); 265 | HANDLE hReadEvt = CreateEvent(NULL, TRUE, FALSE, NULL); 266 | 267 | DWORD oldMode = 0; 268 | GetConsoleMode(hInput, &oldMode); 269 | //DWORD newMode = oldMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 270 | //SetConsoleMode(hInput, newMode); 271 | 272 | bool bWaitForKeyPress = true; 273 | //detect if input redirected from file (in which case we don't want to wait for keyboard hits) 274 | 275 | //DWORD inputSize = GetFileSize(hInput, NULL); 276 | //if(INVALID_FILE_SIZE != inputSize) 277 | // bWaitForKeyPress = false; 278 | DWORD fileType = GetFileType(hInput); 279 | if (FILE_TYPE_CHAR != fileType) 280 | bWaitForKeyPress = false; 281 | 282 | while(false == gbStop) 283 | { 284 | if(bWaitForKeyPress) 285 | { 286 | bool bBail = false; 287 | while(0 == _kbhit()) 288 | { 289 | if(WAIT_OBJECT_0 == WaitForSingleObject(pLP->hStop, 0)) 290 | { 291 | bBail = true; 292 | break; 293 | } 294 | Sleep(100); 295 | } 296 | if(bBail) 297 | break; 298 | } 299 | 300 | nBytesRead = 0; 301 | 302 | if (FILE_TYPE_PIPE == fileType) 303 | { 304 | OVERLAPPED olR = { 0 }; 305 | olR.hEvent = hReadEvt; 306 | if (!ReadFile(hInput, szInputBuffer, SIZEOF_BUFFER - 1, &nBytesRead, &olR) || (nBytesRead == 0)) 307 | { 308 | DWORD dwErr = GetLastError(); 309 | if (dwErr == ERROR_NO_DATA) 310 | break; 311 | } 312 | 313 | if (gbStop) 314 | break; 315 | 316 | HANDLE waits[2]; 317 | waits[0] = pLP->hStop; 318 | waits[1] = olR.hEvent; 319 | DWORD ret = WaitForMultipleObjects(2, waits, FALSE, INFINITE); 320 | if (ret == WAIT_OBJECT_0) 321 | break; //need to exit 322 | _ASSERT(ret == WAIT_OBJECT_0 + 1); //data in buffer now 323 | GetOverlappedResult(hInput, &olR, &nBytesRead, FALSE); 324 | } 325 | else 326 | { 327 | //if ( !ReadConsole( hInput, szInputBuffer, SIZEOF_BUFFER, &nBytesRead, NULL ) ) -- returns UNICODE which is not what we want 328 | if (!ReadFile(hInput, szInputBuffer, SIZEOF_BUFFER - 1, &nBytesRead, NULL)) 329 | { 330 | DWORD dwErr = GetLastError(); 331 | if (dwErr == ERROR_NO_DATA) 332 | break; 333 | } 334 | } 335 | 336 | if(gbStop) 337 | break; 338 | 339 | if(bWaitForKeyPress) 340 | { 341 | //suppress the input from being printed in the output since it was already shown locally 342 | EnterCriticalSection(&pLP->cs); 343 | szInputBuffer[nBytesRead] = '\0'; 344 | pLP->inputSentToSuppressInOutput.push_back(szInputBuffer); 345 | LeaveCriticalSection(&pLP->cs); 346 | } 347 | 348 | // Send it to remote process' stdin 349 | OVERLAPPED olW = {0}; 350 | olW.hEvent = hWritePipe; 351 | 352 | if (!WriteFile( pLP->pSettings->hStdIn, szInputBuffer, nBytesRead, &nBytesWrote, &olW)) 353 | { 354 | DWORD gle = GetLastError(); 355 | break; 356 | } 357 | 358 | if(gbStop) 359 | break; 360 | 361 | HANDLE waits[2]; 362 | waits[0] = pLP->hStop; 363 | waits[1] = olW.hEvent; 364 | DWORD ret = WaitForMultipleObjects(2, waits, FALSE, INFINITE); 365 | if(ret == WAIT_OBJECT_0) 366 | break; //need to exit 367 | _ASSERT(ret == WAIT_OBJECT_0 + 1); //write finished 368 | 369 | FlushFileBuffers(pLP->pSettings->hStdIn); 370 | } 371 | 372 | CloseHandle(hWritePipe); 373 | CloseHandle(hReadEvt); 374 | 375 | SetConsoleMode(hInput, oldMode); 376 | 377 | InterlockedDecrement(&pLP->workerThreads); 378 | 379 | return 0; 380 | } 381 | 382 | 383 | // Connects to the remote processes stdout, stderr and stdin named pipes 384 | BOOL ConnectToRemotePipes(ListenParam* pLP, DWORD dwRetryCount, DWORD dwRetryTimeOut) 385 | { 386 | SECURITY_DESCRIPTOR SecDesc; 387 | InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION); 388 | SetSecurityDescriptorDacl(&SecDesc, TRUE, NULL, FALSE); 389 | 390 | SECURITY_ATTRIBUTES SecAttrib = {0}; 391 | SecAttrib.nLength = sizeof(SECURITY_ATTRIBUTES); 392 | SecAttrib.lpSecurityDescriptor = &SecDesc;; 393 | SecAttrib.bInheritHandle = TRUE; 394 | 395 | CString remoteOutPipeName = StrFormat(L"\\\\%s\\pipe\\PAExecOut%s%u", pLP->remoteServer, pLP->machineName, pLP->pid); 396 | CString remoteErrPipeName = StrFormat(L"\\\\%s\\pipe\\PAExecErr%s%u", pLP->remoteServer, pLP->machineName, pLP->pid); 397 | CString remoteInPipeName = StrFormat(L"\\\\%s\\pipe\\PAExecIn%s%u", pLP->remoteServer, pLP->machineName, pLP->pid); 398 | 399 | DWORD gle = 0; 400 | 401 | while((false == gbStop) && (dwRetryCount--)) 402 | { 403 | // Connects to StdOut pipe 404 | if ( BAD_HANDLE(pLP->pSettings->hStdOut) ) 405 | if (WaitNamedPipe(remoteOutPipeName, NULL)) 406 | { 407 | pLP->pSettings->hStdOut = CreateFile( 408 | remoteOutPipeName, 409 | GENERIC_READ, 410 | 0, 411 | &SecAttrib, 412 | OPEN_EXISTING, 413 | FILE_ATTRIBUTE_NORMAL, 414 | NULL ); 415 | gle = GetLastError(); 416 | } 417 | 418 | 419 | // Connects to Error pipe 420 | if(BAD_HANDLE(pLP->pSettings->hStdErr) ) 421 | if ( WaitNamedPipe(remoteErrPipeName, NULL ) ) 422 | pLP->pSettings->hStdErr = CreateFile( 423 | remoteErrPipeName, 424 | GENERIC_READ, 425 | 0, 426 | &SecAttrib, 427 | OPEN_EXISTING, 428 | FILE_ATTRIBUTE_NORMAL, 429 | NULL ); 430 | 431 | // Connects to StdIn pipe 432 | if(BAD_HANDLE(pLP->pSettings->hStdIn)) 433 | if(WaitNamedPipe(remoteInPipeName, NULL ) ) 434 | pLP->pSettings->hStdIn = CreateFile( 435 | remoteInPipeName, 436 | GENERIC_WRITE, 437 | 0, 438 | &SecAttrib, 439 | OPEN_EXISTING, 440 | FILE_ATTRIBUTE_NORMAL, 441 | NULL ); 442 | 443 | if( !BAD_HANDLE(pLP->pSettings->hStdErr) && 444 | !BAD_HANDLE(pLP->pSettings->hStdIn) && 445 | !BAD_HANDLE(pLP->pSettings->hStdOut) ) 446 | break; 447 | 448 | // One of the pipes failed, try it again 449 | Sleep(dwRetryTimeOut); 450 | } 451 | 452 | if (BAD_HANDLE(pLP->pSettings->hStdErr) || 453 | BAD_HANDLE(pLP->pSettings->hStdIn) || 454 | BAD_HANDLE(pLP->pSettings->hStdOut) || 455 | (WAIT_OBJECT_0 == WaitForSingleObject(pLP->hStop, 0)) || 456 | gbStop) 457 | { 458 | if((gbStop) || (WAIT_OBJECT_0 == WaitForSingleObject(pLP->hStop, 0))) 459 | return false; 460 | 461 | CloseHandle(pLP->pSettings->hStdErr); 462 | CloseHandle(pLP->pSettings->hStdIn); 463 | CloseHandle(pLP->pSettings->hStdOut); 464 | pLP->pSettings->hStdErr = NULL; 465 | pLP->pSettings->hStdIn = NULL; 466 | pLP->pSettings->hStdOut = NULL; 467 | Log(L"Failed to open remote pipes", true); 468 | return false; 469 | } 470 | 471 | //DWORD mode = 0; 472 | //if(gPIPE_TYPE & PIPE_TYPE_MESSAGE) 473 | // mode |= PIPE_READMODE_MESSAGE; 474 | 475 | //BOOL b = SetNamedPipeHandleState(settings.hStdOut, &mode, NULL, NULL); 476 | //DWORD gle = GetLastError(); 477 | //b &= SetNamedPipeHandleState(settings.hStdErr, &mode, NULL, NULL); 478 | //gle = GetLastError(); 479 | //b &= SetNamedPipeHandleState(settings.hStdIn, &mode, NULL, NULL); 480 | //gle = GetLastError(); 481 | //_ASSERT(b); 482 | 483 | UINT ignored; 484 | 485 | // StdOut 486 | InterlockedIncrement(&pLP->workerThreads); 487 | HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ListenRemoteOutPipeThread, pLP, 0, &ignored); 488 | CloseHandle(h); 489 | 490 | // StdErr 491 | InterlockedIncrement(&pLP->workerThreads); 492 | h = (HANDLE)_beginthreadex(NULL, 0, ListenRemoteErrorPipeThread, pLP, 0, &ignored); 493 | CloseHandle(h); 494 | 495 | // StdIn 496 | InterlockedIncrement(&pLP->workerThreads); 497 | h = (HANDLE)_beginthreadex(NULL, 0, ListenRemoteStdInputPipeThread, pLP, 0, &ignored); 498 | CloseHandle(h); 499 | 500 | #ifdef _DEBUG 501 | Log(L"DEBUG: Connected to remote pipes", false); 502 | #endif 503 | 504 | return true; 505 | } 506 | -------------------------------------------------------------------------------- /InteractiveSession.cpp: -------------------------------------------------------------------------------- 1 | // InteractiveSession.cpp: Handling running in different sessions 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | //this is mostly from: 11 | //http://msdn.microsoft.com/en-us/library/windows/desktop/aa379608(v=vs.85).aspx 12 | 13 | #include "stdafx.h" 14 | #include 15 | #include 16 | #include 17 | #include "PAExec.h" 18 | 19 | #ifdef _DEBUG 20 | #define new DEBUG_NEW 21 | #undef THIS_FILE 22 | static char THIS_FILE[] = __FILE__; 23 | #endif 24 | 25 | 26 | 27 | #define DESKTOP_ALL (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | \ 28 | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | \ 29 | DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | \ 30 | DESKTOP_SWITCHDESKTOP | STANDARD_RIGHTS_REQUIRED) 31 | 32 | #define WINSTA_ALL (WINSTA_ENUMDESKTOPS | WINSTA_READATTRIBUTES | \ 33 | WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP | \ 34 | WINSTA_WRITEATTRIBUTES | WINSTA_ACCESSGLOBALATOMS | \ 35 | WINSTA_EXITWINDOWS | WINSTA_ENUMERATE | WINSTA_READSCREEN | \ 36 | STANDARD_RIGHTS_REQUIRED) 37 | 38 | #define GENERIC_ACCESS (GENERIC_READ | GENERIC_WRITE | \ 39 | GENERIC_EXECUTE | GENERIC_ALL) 40 | 41 | BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid); 42 | BOOL AddAceToDesktop(HDESK hdesk, PSID psid); 43 | BOOL GetLogonSID (HANDLE hToken, PSID *ppsid); 44 | VOID FreeLogonSID (PSID *ppsid); 45 | BOOL RemoveAceFromWindowStation(HWINSTA hwinsta, PSID psid); 46 | BOOL RemoveAceFromDesktop(HDESK hdesk, PSID psid); 47 | DWORD GetInteractiveSessionID(); 48 | BOOL RunningAsLocalSystem(); 49 | 50 | 51 | 52 | void CleanUpInteractiveProcess(CleanupInteractive* pCI) 53 | { 54 | //SetTokenInformation(pCI->hUser, TokenSessionId, &pCI->origSessionID, sizeof(pCI->origSessionID)); 55 | 56 | //// Allow logon SID full access to interactive window station. 57 | //RemoveAceFromWindowStation(hwinsta, pSid); 58 | 59 | //// Allow logon SID full access to interactive desktop. 60 | //RemoveAceFromDesktop(hdesk, pSid); 61 | 62 | //// Free the buffer for the logon SID. 63 | //if (pSid) 64 | // FreeLogonSID(&pSid); 65 | //pSid = NULL; 66 | 67 | //// Close the handles to the interactive window station and desktop. 68 | //if (hwinsta) 69 | // CloseWindowStation(hwinsta); 70 | //hwinsta = NULL; 71 | 72 | //if (hdesk) 73 | // CloseDesktop(hdesk); 74 | //hdesk = NULL; 75 | } 76 | 77 | BOOL CALLBACK EnumWindowStationsProc(LPWSTR lpszWindowStation, LPARAM lParam) 78 | { 79 | Log(StrFormat(L"Seen winstation: %s", lpszWindowStation), (DWORD)0); 80 | return TRUE; 81 | } 82 | 83 | 84 | BOOL PrepForInteractiveProcess(Settings& settings, CleanupInteractive* pCI) 85 | { 86 | EnablePrivilege(SE_TCB_NAME, NULL); 87 | 88 | pCI->bPreped = true; 89 | 90 | //settings.hUser is already set as the -u user, Local System (from -s) or as the account the user originally launched PAExec with 91 | 92 | //figure out which session we need to go into 93 | if((DWORD)-1 == settings.sessionToInteractWith) 94 | { 95 | settings.sessionToInteractWith = GetInteractiveSessionID(); 96 | Log(StrFormat(L"Using SessionID %u (interactive session)", settings.sessionToInteractWith), false); 97 | } 98 | else 99 | Log(StrFormat(L"Using SessionID %u from params", settings.sessionToInteractWith), false); 100 | 101 | if (settings.user.IsEmpty()) 102 | { 103 | if(settings.bUseSystemAccount) 104 | { 105 | HANDLE hProcessToken = INVALID_HANDLE_VALUE; 106 | OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hProcessToken); 107 | DuplicateTokenToIncreaseRights(hProcessToken, __FILE__, __LINE__); 108 | SetTokenInformation(hProcessToken, TokenSessionId, &settings.sessionToInteractWith, sizeof(DWORD)); 109 | settings.hUser = hProcessToken; 110 | return TRUE; 111 | } 112 | 113 | //no user given, but want interactive, so run as the currently logged in user 114 | HANDLE hTmp = INVALID_HANDLE_VALUE; 115 | if ((FALSE == WTSQueryUserToken(settings.sessionToInteractWith, &hTmp)) || (INVALID_HANDLE_VALUE == hTmp)) 116 | Log(StrFormat(L"WTSQueryUserToken failed for session ID %d", settings.sessionToInteractWith), GetLastError()); 117 | 118 | if (INVALID_HANDLE_VALUE != hTmp) 119 | { 120 | Log(L"Using user from WTSQueryUserToken", (DWORD)0); 121 | settings.hUser = hTmp; 122 | DuplicateTokenToIncreaseRights(settings.hUser, __FILE__, __LINE__); 123 | } 124 | return TRUE; 125 | } 126 | 127 | //This is the hard case - interactive with a specific user. Not sure why it doesn't work better 128 | 129 | DuplicateTokenToIncreaseRights(settings.hUser, __FILE__, __LINE__); 130 | pCI->hUser = settings.hUser; 131 | 132 | //DWORD returnedLen = 0; 133 | //if(FALSE == GetTokenInformation(settings.hUser, TokenSessionId, &pCI->origSessionID, sizeof(pCI->origSessionID), &returnedLen)) 134 | // Log(L"GetTokenInformation failed", GetLastError()); 135 | 136 | if(false == EnablePrivilege(SE_TCB_NAME, settings.hUser)) 137 | Log(L"EnablePrivilege failed", GetLastError()); 138 | 139 | if(FALSE == SetTokenInformation(settings.hUser, TokenSessionId, &settings.sessionToInteractWith, sizeof(settings.sessionToInteractWith))) 140 | Log(L"Failed to set interactive token", GetLastError()); 141 | 142 | return TRUE; 143 | 144 | ////START FUNKY STUFF - probably doesn't work because OpenWindowStation will get PAExec's Window Station in session 0, which is not what we want 145 | 146 | // BOOL bResult = FALSE; 147 | // 148 | // HDESK hdesk = NULL; 149 | // HWINSTA hwinsta = NULL; 150 | // PSID pSid = NULL; 151 | // HWINSTA hwinstaSave = NULL; 152 | // USEROBJECTFLAGS uof = { 0 }; 153 | // DWORD needed = 0; 154 | // 155 | // // Save a handle to the caller's current window station. 156 | // if ((hwinstaSave = GetProcessWindowStation()) == NULL) 157 | // { 158 | // Log(L"Failed to get GetProcessWindowStation.", GetLastError()); 159 | // goto Cleanup; 160 | // } 161 | // 162 | // LPCWSTR winStaToUse = L"WinSta0"; 163 | // 164 | // // Get a handle to the interactive window station. 165 | // hwinsta = OpenWindowStation( 166 | // winStaToUse, // the interactive window station 167 | // FALSE, // handle is not inheritable 168 | // READ_CONTROL | WRITE_DAC | WINSTA_READATTRIBUTES); // rights to read/write the DACL 169 | // 170 | // if (BAD_HANDLE(hwinsta)) 171 | // { 172 | // Log(StrFormat(L"Failed to open WinStation %s.", winStaToUse), GetLastError()); 173 | // EnumWindowStations(EnumWindowStationsProc, NULL); 174 | // 175 | // goto Cleanup; 176 | // } 177 | // 178 | // //Some logging 179 | // if(GetUserObjectInformation(hwinsta, UOI_FLAGS, &uof, sizeof(uof), &needed)) 180 | // Log(StrFormat(L"WinStation visible: %d", uof.dwFlags), (DWORD)0); 181 | // else 182 | // Log(L"GetUserObjectInformation failed", GetLastError()); 183 | // 184 | // // To get the correct default desktop, set the caller's 185 | // // window station to the interactive window station. 186 | // if (!SetProcessWindowStation(hwinsta)) 187 | // { 188 | // Log(L"Failed to SetProcessWindowStation.", GetLastError()); 189 | // goto Cleanup; 190 | // } 191 | // 192 | // // Get a handle to the interactive desktop. 193 | // hdesk = OpenDesktop( 194 | // _T("default"), // the interactive window station 195 | // 0, // no interaction with other desktop processes 196 | // FALSE, // handle is not inheritable 197 | // READ_CONTROL | // request the rights to read and write the DACL 198 | // WRITE_DAC | 199 | // DESKTOP_WRITEOBJECTS | 200 | // DESKTOP_READOBJECTS); 201 | // 202 | // DWORD gle = GetLastError(); 203 | // 204 | // // Restore the caller's window station. 205 | // //if (!SetProcessWindowStation(hwinstaSave)) 206 | // // goto Cleanup; 207 | // 208 | // if (BAD_HANDLE(hdesk)) 209 | // { 210 | // Log(L"Failed to get Default desktop.", gle); 211 | // goto Cleanup; 212 | // } 213 | // 214 | // // Get the SID for the client's logon session. 215 | // if (!GetLogonSID(pCI->hUser, &pSid)) 216 | // { 217 | // Log(L"Failed to get login SID.", true); 218 | // goto Cleanup; 219 | // } 220 | // 221 | // // Allow logon SID full access to interactive window station. 222 | // if (! AddAceToWindowStation(hwinsta, pSid) ) 223 | // { 224 | // Log(L"Failed to add ACE to WinStation.", GetLastError()); 225 | // CloseWindowStation(hwinsta); 226 | // hwinsta = NULL; //so it's not removed and cleaned up later 227 | // goto Cleanup; 228 | // } 229 | // 230 | // // Allow logon SID full access to interactive desktop. 231 | // if (! AddAceToDesktop(hdesk, pSid) ) 232 | // { 233 | // Log(L"Failed to add ACE to Desktop.", GetLastError()); 234 | // CloseDesktop(hdesk); 235 | // hdesk = NULL; 236 | // goto Cleanup; 237 | // } 238 | // 239 | ////END FUNKY STUFF 240 | // 241 | // bResult = TRUE; 242 | // 243 | //Cleanup: 244 | //// if (!BAD_HANDLE(hwinstaSave)) 245 | //// SetProcessWindowStation (hwinstaSave); 246 | // 247 | // return bResult; 248 | } 249 | 250 | BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid) 251 | { 252 | ACCESS_ALLOWED_ACE *pace = NULL; 253 | ACL_SIZE_INFORMATION aclSizeInfo; 254 | BOOL bDaclExist; 255 | BOOL bDaclPresent; 256 | BOOL bSuccess = FALSE; 257 | DWORD dwNewAclSize; 258 | DWORD dwSidSize = 0; 259 | DWORD dwSdSizeNeeded; 260 | PACL pacl; 261 | PACL pNewAcl = NULL; 262 | PSECURITY_DESCRIPTOR psd = NULL; 263 | PSECURITY_DESCRIPTOR psdNew = NULL; 264 | PVOID pTempAce; 265 | SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION; 266 | unsigned int i; 267 | 268 | __try 269 | { 270 | // Obtain the DACL for the window station. 271 | 272 | if (!GetUserObjectSecurity( 273 | hwinsta, 274 | &si, 275 | psd, 276 | dwSidSize, 277 | &dwSdSizeNeeded) 278 | ) 279 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) 280 | { 281 | psd = (PSECURITY_DESCRIPTOR)HeapAlloc( 282 | GetProcessHeap(), 283 | HEAP_ZERO_MEMORY, 284 | dwSdSizeNeeded); 285 | 286 | if (psd == NULL) 287 | __leave; 288 | 289 | psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc( 290 | GetProcessHeap(), 291 | HEAP_ZERO_MEMORY, 292 | dwSdSizeNeeded); 293 | 294 | if (psdNew == NULL) 295 | __leave; 296 | 297 | dwSidSize = dwSdSizeNeeded; 298 | 299 | if (!GetUserObjectSecurity( 300 | hwinsta, 301 | &si, 302 | psd, 303 | dwSidSize, 304 | &dwSdSizeNeeded) 305 | ) 306 | __leave; 307 | } 308 | else 309 | __leave; 310 | 311 | // Create a new DACL. 312 | 313 | if (!InitializeSecurityDescriptor( 314 | psdNew, 315 | SECURITY_DESCRIPTOR_REVISION) 316 | ) 317 | __leave; 318 | 319 | // Get the DACL from the security descriptor. 320 | 321 | if (!GetSecurityDescriptorDacl( 322 | psd, 323 | &bDaclPresent, 324 | &pacl, 325 | &bDaclExist) 326 | ) 327 | __leave; 328 | 329 | // Initialize the ACL. 330 | 331 | ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION)); 332 | aclSizeInfo.AclBytesInUse = sizeof(ACL); 333 | 334 | // Call only if the DACL is not NULL. 335 | 336 | if (pacl != NULL) 337 | { 338 | // get the file ACL size info 339 | if (!GetAclInformation( 340 | pacl, 341 | (LPVOID)&aclSizeInfo, 342 | sizeof(ACL_SIZE_INFORMATION), 343 | AclSizeInformation) 344 | ) 345 | __leave; 346 | } 347 | 348 | // Compute the size of the new ACL. 349 | 350 | dwNewAclSize = aclSizeInfo.AclBytesInUse + 351 | (2*sizeof(ACCESS_ALLOWED_ACE)) + (2*GetLengthSid(psid)) - 352 | (2*sizeof(DWORD)); 353 | 354 | // Allocate memory for the new ACL. 355 | 356 | pNewAcl = (PACL)HeapAlloc( 357 | GetProcessHeap(), 358 | HEAP_ZERO_MEMORY, 359 | dwNewAclSize); 360 | 361 | if (pNewAcl == NULL) 362 | __leave; 363 | 364 | // Initialize the new DACL. 365 | 366 | if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION)) 367 | __leave; 368 | 369 | // If DACL is present, copy it to a new DACL. 370 | 371 | if (bDaclPresent) 372 | { 373 | // Copy the ACEs to the new ACL. 374 | if (aclSizeInfo.AceCount) 375 | { 376 | for (i=0; i < aclSizeInfo.AceCount; i++) 377 | { 378 | // Get an ACE. 379 | if (!GetAce(pacl, i, &pTempAce)) 380 | __leave; 381 | 382 | // Add the ACE to the new ACL. 383 | if (!AddAce( 384 | pNewAcl, 385 | ACL_REVISION, 386 | MAXDWORD, 387 | pTempAce, 388 | ((PACE_HEADER)pTempAce)->AceSize) 389 | ) 390 | __leave; 391 | } 392 | } 393 | } 394 | 395 | // Add the first ACE to the window station. 396 | 397 | pace = (ACCESS_ALLOWED_ACE *)HeapAlloc( 398 | GetProcessHeap(), 399 | HEAP_ZERO_MEMORY, 400 | sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid) - 401 | sizeof(DWORD)); 402 | 403 | if (pace == NULL) 404 | __leave; 405 | 406 | pace->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; 407 | pace->Header.AceFlags = CONTAINER_INHERIT_ACE | 408 | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE; 409 | pace->Header.AceSize = LOWORD(sizeof(ACCESS_ALLOWED_ACE) + 410 | GetLengthSid(psid) - sizeof(DWORD)); 411 | pace->Mask = GENERIC_ACCESS; 412 | 413 | if (!CopySid(GetLengthSid(psid), &pace->SidStart, psid)) 414 | __leave; 415 | 416 | if (!AddAce( 417 | pNewAcl, 418 | ACL_REVISION, 419 | MAXDWORD, 420 | (LPVOID)pace, 421 | pace->Header.AceSize) 422 | ) 423 | __leave; 424 | 425 | // Add the second ACE to the window station. 426 | 427 | pace->Header.AceFlags = NO_PROPAGATE_INHERIT_ACE; 428 | pace->Mask = WINSTA_ALL; 429 | 430 | if (!AddAce( 431 | pNewAcl, 432 | ACL_REVISION, 433 | MAXDWORD, 434 | (LPVOID)pace, 435 | pace->Header.AceSize) 436 | ) 437 | __leave; 438 | 439 | // Set a new DACL for the security descriptor. 440 | 441 | if (!SetSecurityDescriptorDacl( 442 | psdNew, 443 | TRUE, 444 | pNewAcl, 445 | FALSE) 446 | ) 447 | __leave; 448 | 449 | // Set the new security descriptor for the window station. 450 | 451 | if (!SetUserObjectSecurity(hwinsta, &si, psdNew)) 452 | __leave; 453 | 454 | // Indicate success. 455 | 456 | bSuccess = TRUE; 457 | } 458 | __finally 459 | { 460 | // Free the allocated buffers. 461 | 462 | if (pace != NULL) 463 | HeapFree(GetProcessHeap(), 0, (LPVOID)pace); 464 | 465 | if (pNewAcl != NULL) 466 | HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl); 467 | 468 | if (psd != NULL) 469 | HeapFree(GetProcessHeap(), 0, (LPVOID)psd); 470 | 471 | if (psdNew != NULL) 472 | HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew); 473 | } 474 | 475 | return bSuccess; 476 | 477 | } 478 | 479 | BOOL AddAceToDesktop(HDESK hdesk, PSID psid) 480 | { 481 | ACL_SIZE_INFORMATION aclSizeInfo; 482 | BOOL bDaclExist; 483 | BOOL bDaclPresent; 484 | BOOL bSuccess = FALSE; 485 | DWORD dwNewAclSize; 486 | DWORD dwSidSize = 0; 487 | DWORD dwSdSizeNeeded; 488 | PACL pacl; 489 | PACL pNewAcl = NULL; 490 | PSECURITY_DESCRIPTOR psd = NULL; 491 | PSECURITY_DESCRIPTOR psdNew = NULL; 492 | PVOID pTempAce; 493 | SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION; 494 | unsigned int i; 495 | 496 | __try 497 | { 498 | // Obtain the security descriptor for the desktop object. 499 | 500 | if (!GetUserObjectSecurity( 501 | hdesk, 502 | &si, 503 | psd, 504 | dwSidSize, 505 | &dwSdSizeNeeded)) 506 | { 507 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) 508 | { 509 | psd = (PSECURITY_DESCRIPTOR)HeapAlloc( 510 | GetProcessHeap(), 511 | HEAP_ZERO_MEMORY, 512 | dwSdSizeNeeded ); 513 | 514 | if (psd == NULL) 515 | __leave; 516 | 517 | psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc( 518 | GetProcessHeap(), 519 | HEAP_ZERO_MEMORY, 520 | dwSdSizeNeeded); 521 | 522 | if (psdNew == NULL) 523 | __leave; 524 | 525 | dwSidSize = dwSdSizeNeeded; 526 | 527 | if (!GetUserObjectSecurity( 528 | hdesk, 529 | &si, 530 | psd, 531 | dwSidSize, 532 | &dwSdSizeNeeded) 533 | ) 534 | __leave; 535 | } 536 | else 537 | __leave; 538 | } 539 | 540 | // Create a new security descriptor. 541 | 542 | if (!InitializeSecurityDescriptor( 543 | psdNew, 544 | SECURITY_DESCRIPTOR_REVISION) 545 | ) 546 | __leave; 547 | 548 | // Obtain the DACL from the security descriptor. 549 | 550 | if (!GetSecurityDescriptorDacl( 551 | psd, 552 | &bDaclPresent, 553 | &pacl, 554 | &bDaclExist) 555 | ) 556 | __leave; 557 | 558 | // Initialize. 559 | 560 | ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION)); 561 | aclSizeInfo.AclBytesInUse = sizeof(ACL); 562 | 563 | // Call only if NULL DACL. 564 | 565 | if (pacl != NULL) 566 | { 567 | // Determine the size of the ACL information. 568 | 569 | if (!GetAclInformation( 570 | pacl, 571 | (LPVOID)&aclSizeInfo, 572 | sizeof(ACL_SIZE_INFORMATION), 573 | AclSizeInformation) 574 | ) 575 | __leave; 576 | } 577 | 578 | // Compute the size of the new ACL. 579 | 580 | dwNewAclSize = aclSizeInfo.AclBytesInUse + 581 | sizeof(ACCESS_ALLOWED_ACE) + 582 | GetLengthSid(psid) - sizeof(DWORD); 583 | 584 | // Allocate buffer for the new ACL. 585 | 586 | pNewAcl = (PACL)HeapAlloc( 587 | GetProcessHeap(), 588 | HEAP_ZERO_MEMORY, 589 | dwNewAclSize); 590 | 591 | if (pNewAcl == NULL) 592 | __leave; 593 | 594 | // Initialize the new ACL. 595 | 596 | if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION)) 597 | __leave; 598 | 599 | // If DACL is present, copy it to a new DACL. 600 | 601 | if (bDaclPresent) 602 | { 603 | // Copy the ACEs to the new ACL. 604 | if (aclSizeInfo.AceCount) 605 | { 606 | for (i=0; i < aclSizeInfo.AceCount; i++) 607 | { 608 | // Get an ACE. 609 | if (!GetAce(pacl, i, &pTempAce)) 610 | __leave; 611 | 612 | // Add the ACE to the new ACL. 613 | if (!AddAce( 614 | pNewAcl, 615 | ACL_REVISION, 616 | MAXDWORD, 617 | pTempAce, 618 | ((PACE_HEADER)pTempAce)->AceSize) 619 | ) 620 | __leave; 621 | } 622 | } 623 | } 624 | 625 | // Add ACE to the DACL. 626 | 627 | if (!AddAccessAllowedAce( 628 | pNewAcl, 629 | ACL_REVISION, 630 | DESKTOP_ALL, 631 | psid) 632 | ) 633 | __leave; 634 | 635 | // Set new DACL to the new security descriptor. 636 | 637 | if (!SetSecurityDescriptorDacl( 638 | psdNew, 639 | TRUE, 640 | pNewAcl, 641 | FALSE) 642 | ) 643 | __leave; 644 | 645 | // Set the new security descriptor for the desktop object. 646 | 647 | if (!SetUserObjectSecurity(hdesk, &si, psdNew)) 648 | __leave; 649 | 650 | // Indicate success. 651 | 652 | bSuccess = TRUE; 653 | } 654 | __finally 655 | { 656 | // Free buffers. 657 | 658 | if (pNewAcl != NULL) 659 | HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl); 660 | 661 | if (psd != NULL) 662 | HeapFree(GetProcessHeap(), 0, (LPVOID)psd); 663 | 664 | if (psdNew != NULL) 665 | HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew); 666 | } 667 | 668 | return bSuccess; 669 | } 670 | 671 | 672 | 673 | 674 | BOOL RemoveAceFromWindowStation(HWINSTA hwinsta, PSID psid) 675 | { 676 | ACL_SIZE_INFORMATION aclSizeInfo; 677 | BOOL bDaclExist; 678 | BOOL bDaclPresent; 679 | BOOL bSuccess = FALSE; 680 | DWORD dwNewAclSize; 681 | DWORD dwSidSize = 0; 682 | DWORD dwSdSizeNeeded; 683 | PACL pacl; 684 | PACL pNewAcl; 685 | PSECURITY_DESCRIPTOR psd = NULL; 686 | PSECURITY_DESCRIPTOR psdNew = NULL; 687 | ACCESS_ALLOWED_ACE* pTempAce; 688 | SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION; 689 | unsigned int i; 690 | 691 | __try 692 | { 693 | // Obtain the DACL for the window station. 694 | 695 | if (!GetUserObjectSecurity( 696 | hwinsta, 697 | &si, 698 | psd, 699 | dwSidSize, 700 | &dwSdSizeNeeded) 701 | ) 702 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) 703 | { 704 | psd = (PSECURITY_DESCRIPTOR)HeapAlloc( 705 | GetProcessHeap(), 706 | HEAP_ZERO_MEMORY, 707 | dwSdSizeNeeded); 708 | 709 | if (psd == NULL) 710 | __leave; 711 | 712 | psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc( 713 | GetProcessHeap(), 714 | HEAP_ZERO_MEMORY, 715 | dwSdSizeNeeded); 716 | 717 | if (psdNew == NULL) 718 | __leave; 719 | 720 | dwSidSize = dwSdSizeNeeded; 721 | 722 | if (!GetUserObjectSecurity( 723 | hwinsta, 724 | &si, 725 | psd, 726 | dwSidSize, 727 | &dwSdSizeNeeded) 728 | ) 729 | __leave; 730 | } 731 | else 732 | __leave; 733 | 734 | // Create a new DACL. 735 | 736 | if (!InitializeSecurityDescriptor( 737 | psdNew, 738 | SECURITY_DESCRIPTOR_REVISION) 739 | ) 740 | __leave; 741 | 742 | // Get the DACL from the security descriptor. 743 | 744 | if (!GetSecurityDescriptorDacl( 745 | psd, 746 | &bDaclPresent, 747 | &pacl, 748 | &bDaclExist) 749 | ) 750 | __leave; 751 | 752 | // Initialize the ACL. 753 | 754 | ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION)); 755 | aclSizeInfo.AclBytesInUse = sizeof(ACL); 756 | 757 | // Call only if the DACL is not NULL. 758 | 759 | if (pacl != NULL) 760 | { 761 | // get the file ACL size info 762 | if (!GetAclInformation( 763 | pacl, 764 | (LPVOID)&aclSizeInfo, 765 | sizeof(ACL_SIZE_INFORMATION), 766 | AclSizeInformation) 767 | ) 768 | __leave; 769 | } 770 | 771 | // Compute the size of the new ACL. 772 | 773 | dwNewAclSize = aclSizeInfo.AclBytesInUse + 774 | (2*sizeof(ACCESS_ALLOWED_ACE)) + (2*GetLengthSid(psid)) - 775 | (2*sizeof(DWORD)); 776 | 777 | // Allocate memory for the new ACL. 778 | 779 | pNewAcl = (PACL)HeapAlloc( 780 | GetProcessHeap(), 781 | HEAP_ZERO_MEMORY, 782 | dwNewAclSize); 783 | 784 | if (pNewAcl == NULL) 785 | __leave; 786 | 787 | // Initialize the new DACL. 788 | 789 | if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION)) 790 | __leave; 791 | 792 | // If DACL is present, copy it to a new DACL. 793 | 794 | if (bDaclPresent) 795 | { 796 | // Copy the ACEs to the new ACL. 797 | if (aclSizeInfo.AceCount) 798 | { 799 | for (i=0; i < aclSizeInfo.AceCount; i++) 800 | { 801 | // Get an ACE. 802 | if (!GetAce(pacl, i, reinterpret_cast(&pTempAce))) 803 | __leave; 804 | 805 | if ( !EqualSid( psid, &pTempAce->SidStart ) ) 806 | { 807 | 808 | // Add the ACE to the new ACL. 809 | if (!AddAce( 810 | pNewAcl, 811 | ACL_REVISION, 812 | MAXDWORD, 813 | pTempAce, 814 | ((PACE_HEADER)pTempAce)->AceSize) 815 | ) 816 | __leave; 817 | } 818 | } 819 | } 820 | } 821 | 822 | if( pacl != NULL ) 823 | HeapFree(GetProcessHeap(), 0, (LPVOID)pacl); 824 | 825 | // Set a new DACL for the security descriptor. 826 | 827 | if (!SetSecurityDescriptorDacl( 828 | psdNew, 829 | TRUE, 830 | pNewAcl, 831 | FALSE) 832 | ) 833 | __leave; 834 | 835 | // Set the new security descriptor for the window station. 836 | 837 | if (!SetUserObjectSecurity(hwinsta, &si, psdNew)) 838 | __leave; 839 | 840 | // Indicate success. 841 | 842 | bSuccess = TRUE; 843 | } 844 | __finally 845 | { 846 | // Free the allocated buffers. 847 | 848 | if( pacl != NULL ) 849 | HeapFree(GetProcessHeap(), 0, (LPVOID)pacl); 850 | 851 | if (pNewAcl != NULL) 852 | HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl); 853 | 854 | if (psd != NULL) 855 | HeapFree(GetProcessHeap(), 0, (LPVOID)psd); 856 | 857 | if (psdNew != NULL) 858 | HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew); 859 | } 860 | 861 | return bSuccess; 862 | 863 | } 864 | 865 | BOOL RemoveAceFromDesktop(HDESK hdesk, PSID psid) 866 | { 867 | ACL_SIZE_INFORMATION aclSizeInfo; 868 | BOOL bDaclExist; 869 | BOOL bDaclPresent; 870 | BOOL bSuccess = FALSE; 871 | DWORD dwNewAclSize; 872 | DWORD dwSidSize = 0; 873 | DWORD dwSdSizeNeeded; 874 | PACL pacl; 875 | PACL pNewAcl; 876 | PSECURITY_DESCRIPTOR psd = NULL; 877 | PSECURITY_DESCRIPTOR psdNew = NULL; 878 | ACCESS_ALLOWED_ACE* pTempAce; 879 | SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION; 880 | unsigned int i; 881 | 882 | __try 883 | { 884 | // Obtain the security descriptor for the desktop object. 885 | 886 | if (!GetUserObjectSecurity( 887 | hdesk, 888 | &si, 889 | psd, 890 | dwSidSize, 891 | &dwSdSizeNeeded)) 892 | { 893 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) 894 | { 895 | psd = (PSECURITY_DESCRIPTOR)HeapAlloc( 896 | GetProcessHeap(), 897 | HEAP_ZERO_MEMORY, 898 | dwSdSizeNeeded ); 899 | 900 | if (psd == NULL) 901 | __leave; 902 | 903 | psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc( 904 | GetProcessHeap(), 905 | HEAP_ZERO_MEMORY, 906 | dwSdSizeNeeded); 907 | 908 | if (psdNew == NULL) 909 | __leave; 910 | 911 | dwSidSize = dwSdSizeNeeded; 912 | 913 | if (!GetUserObjectSecurity( 914 | hdesk, 915 | &si, 916 | psd, 917 | dwSidSize, 918 | &dwSdSizeNeeded) 919 | ) 920 | __leave; 921 | } 922 | else 923 | __leave; 924 | } 925 | 926 | // Create a new security descriptor. 927 | 928 | if (!InitializeSecurityDescriptor( 929 | psdNew, 930 | SECURITY_DESCRIPTOR_REVISION) 931 | ) 932 | __leave; 933 | 934 | // Obtain the DACL from the security descriptor. 935 | 936 | if (!GetSecurityDescriptorDacl( 937 | psd, 938 | &bDaclPresent, 939 | &pacl, 940 | &bDaclExist) 941 | ) 942 | __leave; 943 | 944 | // Initialize. 945 | 946 | ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION)); 947 | aclSizeInfo.AclBytesInUse = sizeof(ACL); 948 | 949 | // Call only if NULL DACL. 950 | 951 | if (pacl != NULL) 952 | { 953 | // Determine the size of the ACL information. 954 | 955 | if (!GetAclInformation( 956 | pacl, 957 | (LPVOID)&aclSizeInfo, 958 | sizeof(ACL_SIZE_INFORMATION), 959 | AclSizeInformation) 960 | ) 961 | __leave; 962 | } 963 | 964 | // Compute the size of the new ACL. 965 | 966 | dwNewAclSize = aclSizeInfo.AclBytesInUse + 967 | sizeof(ACCESS_ALLOWED_ACE) + 968 | GetLengthSid(psid) - sizeof(DWORD); 969 | 970 | // Allocate buffer for the new ACL. 971 | 972 | pNewAcl = (PACL)HeapAlloc( 973 | GetProcessHeap(), 974 | HEAP_ZERO_MEMORY, 975 | dwNewAclSize); 976 | 977 | if (pNewAcl == NULL) 978 | __leave; 979 | 980 | // Initialize the new ACL. 981 | 982 | if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION)) 983 | __leave; 984 | 985 | // If DACL is present, copy it to a new DACL. 986 | 987 | if (bDaclPresent) 988 | { 989 | // Copy the ACEs to the new ACL. 990 | if (aclSizeInfo.AceCount) 991 | { 992 | for (i=0; i < aclSizeInfo.AceCount; i++) 993 | { 994 | // Get an ACE. 995 | if (!GetAce(pacl, i, reinterpret_cast(&pTempAce))) 996 | __leave; 997 | 998 | if ( !EqualSid( psid, &pTempAce->SidStart ) ) 999 | { 1000 | 1001 | // Add the ACE to the new ACL. 1002 | if (!AddAce( 1003 | pNewAcl, 1004 | ACL_REVISION, 1005 | MAXDWORD, 1006 | pTempAce, 1007 | ((PACE_HEADER)pTempAce)->AceSize) 1008 | ) 1009 | __leave; 1010 | } 1011 | } 1012 | } 1013 | } 1014 | 1015 | // Set new DACL to the new security descriptor. 1016 | 1017 | if (!SetSecurityDescriptorDacl( 1018 | psdNew, 1019 | TRUE, 1020 | pNewAcl, 1021 | FALSE) 1022 | ) 1023 | __leave; 1024 | 1025 | // Set the new security descriptor for the desktop object. 1026 | 1027 | if (!SetUserObjectSecurity(hdesk, &si, psdNew)) 1028 | __leave; 1029 | 1030 | // Indicate success. 1031 | 1032 | bSuccess = TRUE; 1033 | } 1034 | __finally 1035 | { 1036 | // Free buffers. 1037 | 1038 | if( pacl != NULL ) 1039 | HeapFree(GetProcessHeap(), 0, (LPVOID)pacl); 1040 | 1041 | if (pNewAcl != NULL) 1042 | HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl); 1043 | 1044 | if (psd != NULL) 1045 | HeapFree(GetProcessHeap(), 0, (LPVOID)psd); 1046 | 1047 | if (psdNew != NULL) 1048 | HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew); 1049 | } 1050 | 1051 | return bSuccess; 1052 | } 1053 | 1054 | 1055 | 1056 | BOOL GetLogonSID (HANDLE hToken, PSID *ppsid) 1057 | { 1058 | BOOL bSuccess = FALSE; 1059 | DWORD dwIndex; 1060 | DWORD dwLength = 0; 1061 | PTOKEN_GROUPS ptg = NULL; 1062 | PTOKEN_USER pTU = NULL; 1063 | 1064 | // Verify the parameter passed in is not NULL. 1065 | if (NULL == ppsid) 1066 | goto Cleanup; 1067 | 1068 | GetTokenInformation(hToken, TokenUser, pTU, 0, &dwLength); 1069 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 1070 | { 1071 | Log(L"Failed to get login token information", GetLastError()); 1072 | goto Cleanup; 1073 | } 1074 | 1075 | pTU = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); 1076 | if (pTU == NULL) 1077 | goto Cleanup; 1078 | 1079 | if(GetTokenInformation(hToken, TokenUser, pTU, dwLength, &dwLength)) 1080 | { 1081 | //try to get SID 1082 | dwLength = GetLengthSid(pTU->User.Sid); 1083 | *ppsid = (PSID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); 1084 | if (*ppsid == NULL) 1085 | goto Cleanup; 1086 | if (!CopySid(dwLength, *ppsid, pTU->User.Sid)) 1087 | { 1088 | HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); 1089 | goto Cleanup; 1090 | } 1091 | bSuccess = TRUE; 1092 | goto Cleanup; 1093 | } 1094 | 1095 | //fall through and make alternate attempt 1096 | 1097 | // Get required buffer size and allocate the TOKEN_GROUPS buffer. 1098 | if (!GetTokenInformation( 1099 | hToken, // handle to the access token 1100 | TokenGroups, // get information about the token's groups 1101 | (LPVOID) ptg, // pointer to TOKEN_GROUPS buffer 1102 | 0, // size of buffer 1103 | &dwLength // receives required buffer size 1104 | )) 1105 | { 1106 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 1107 | { 1108 | Log(L"Failed to get login token information[2]", GetLastError()); 1109 | goto Cleanup; 1110 | } 1111 | 1112 | ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); 1113 | 1114 | if (ptg == NULL) 1115 | goto Cleanup; 1116 | } 1117 | 1118 | // Get the token group information from the access token. 1119 | 1120 | if (!GetTokenInformation( 1121 | hToken, // handle to the access token 1122 | TokenGroups, // get information about the token's groups 1123 | (LPVOID) ptg, // pointer to TOKEN_GROUPS buffer 1124 | dwLength, // size of buffer 1125 | &dwLength // receives required buffer size 1126 | )) 1127 | { 1128 | goto Cleanup; 1129 | } 1130 | 1131 | // Loop through the groups to find the logon SID. 1132 | 1133 | for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++) 1134 | if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) 1135 | == SE_GROUP_LOGON_ID) 1136 | { 1137 | // Found the logon SID; make a copy of it. 1138 | 1139 | dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid); 1140 | *ppsid = (PSID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); 1141 | if (*ppsid == NULL) 1142 | goto Cleanup; 1143 | if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid)) 1144 | { 1145 | HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); 1146 | goto Cleanup; 1147 | } 1148 | bSuccess = TRUE; 1149 | break; 1150 | } 1151 | 1152 | 1153 | Cleanup: 1154 | if (ptg != NULL) 1155 | HeapFree(GetProcessHeap(), 0, (LPVOID)ptg); 1156 | ptg = NULL; 1157 | if(NULL != pTU) 1158 | HeapFree(GetProcessHeap(), 0, pTU); 1159 | pTU = NULL; 1160 | 1161 | return bSuccess; 1162 | } 1163 | 1164 | 1165 | VOID FreeLogonSID (PSID *ppsid) 1166 | { 1167 | HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); 1168 | } 1169 | 1170 | 1171 | typedef DWORD (WINAPI *WTSGetActiveConsoleSessionIdProc)(void); 1172 | 1173 | DWORD GetInteractiveSessionID() 1174 | { 1175 | // Get the active session ID. 1176 | DWORD SessionId = (DWORD)-1; 1177 | PWTS_SESSION_INFO pSessionInfo; 1178 | DWORD Count = 0; 1179 | 1180 | if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, & pSessionInfo, & Count)) 1181 | { 1182 | for (DWORD i = 0; i < Count; i ++) 1183 | { 1184 | if (pSessionInfo [i].State == WTSActive) //Here is 1185 | SessionId = pSessionInfo[i].SessionId; 1186 | } 1187 | WTSFreeMemory (pSessionInfo); 1188 | } 1189 | 1190 | static WTSGetActiveConsoleSessionIdProc pWTSGetActiveConsoleSessionId = NULL; 1191 | if(NULL == pWTSGetActiveConsoleSessionId) 1192 | { 1193 | HMODULE hMod = LoadLibrary(L"Kernel32.dll"); //GLOK 1194 | if(NULL != hMod) 1195 | { 1196 | pWTSGetActiveConsoleSessionId = (WTSGetActiveConsoleSessionIdProc)GetProcAddress(hMod, "WTSGetActiveConsoleSessionId"); 1197 | } 1198 | } 1199 | 1200 | if(NULL != pWTSGetActiveConsoleSessionId) //not supported on Win2K 1201 | { 1202 | DWORD tmp = pWTSGetActiveConsoleSessionId(); //we fall back on this if needed since it apparently doesn't always work 1203 | if(0 == SessionId) 1204 | SessionId = tmp; 1205 | else 1206 | { 1207 | if(tmp != SessionId) 1208 | Log(StrFormat(L"WTSEnumerateSessions found session ID %u, but WTSGetActiveConsoleSessionId returned %u. Using %u.", SessionId, tmp, SessionId), (DWORD)0); 1209 | } 1210 | } 1211 | else 1212 | Log(L"WTSGetActiveConsoleSessionId not supported on this OS", false); 1213 | 1214 | return SessionId; 1215 | } 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | BOOL RunningAsLocalSystem() 1223 | { 1224 | HANDLE hToken; 1225 | 1226 | // open process token 1227 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) 1228 | return FALSE; 1229 | 1230 | UCHAR bTokenUser[sizeof(TOKEN_USER) + 8 + 4 * SID_MAX_SUB_AUTHORITIES]; 1231 | PTOKEN_USER pTokenUser = (PTOKEN_USER)bTokenUser; 1232 | ULONG cbTokenUser; 1233 | 1234 | // retrieve user SID 1235 | if (!GetTokenInformation(hToken, TokenUser, pTokenUser, sizeof(bTokenUser), &cbTokenUser)) 1236 | { 1237 | CloseHandle(hToken); 1238 | return FALSE; 1239 | } 1240 | 1241 | CloseHandle(hToken); 1242 | 1243 | SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY; 1244 | PSID pSystemSid; 1245 | 1246 | // allocate LocalSystem well-known SID 1247 | if (!AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSystemSid)) 1248 | return FALSE; 1249 | 1250 | // compare the user SID from the token with the LocalSystem SID 1251 | BOOL bSystem = EqualSid(pTokenUser->User.Sid, pSystemSid); 1252 | 1253 | FreeSid(pSystemSid); 1254 | 1255 | return bSystem; 1256 | } 1257 | -------------------------------------------------------------------------------- /PAExec.cpp: -------------------------------------------------------------------------------- 1 | // PAExec.cpp: Main program file 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | 11 | #include "stdafx.h" 12 | #include "PAExec.h" 13 | #include "CmdLineParser.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | //////////////////////////////////////////////////////////////////////// 21 | //v1.31 - Feb 19, 2025 22 | // Better checking for impersonation handle value, which will quiet some false errors 23 | // 24 | //v1.30 - Feb 2, 2023 25 | // Tried to make interactive work better. Works OK if no username/password is given, and if running as the system account is wanted, but 26 | // still doesn't work well when credentials are given. 27 | // 28 | //v1.29 - April 14, 2021 29 | // will connect with given credentials before automatically trying file copy and service installation 30 | // support redirected input from file 31 | // don't show window if -i flag isn't given 32 | // better cleanup of remote service 33 | // 34 | 35 | 36 | 37 | bool gbODS = false; 38 | CString gLogPath; 39 | 40 | // The one and only application object 41 | //CWinApp theApp; 42 | void RegressionTests(); 43 | BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType); 44 | bool CheckTimeout(__time64_t start, LPCWSTR serverName, Settings& settings); 45 | 46 | #ifdef _DEBUG 47 | bool bWaited = false; 48 | void WaitSomewhere() 49 | { 50 | if(bWaited) 51 | return; 52 | bWaited = true; 53 | wprintf(L"\r\nDEBUG: Waiting for key press\r\n"); 54 | _getch(); 55 | } 56 | #endif 57 | 58 | int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) 59 | { 60 | int exitCode = 0; 61 | bool bPrintExitCode = true; 62 | 63 | //// initialize MFC and print and error on failure 64 | //if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) 65 | //{ 66 | // Log(L"Fatal Error: MFC initialization failed", true); 67 | // exitCode = -1; 68 | //} 69 | //else 70 | //{ 71 | VERIFY(SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)); 72 | 73 | CCmdLineParser cmdParser; 74 | cmdParser.Parse(::GetCommandLine()); 75 | 76 | if(cmdParser.HasKey(L"service")) 77 | { 78 | //Service Control Manager launched us -- we're a service! 79 | return StartLocalService(cmdParser); 80 | } 81 | 82 | Settings settings; 83 | 84 | //read early so logging is started as soon as possible 85 | if(cmdParser.HasKey(L"dbg")) 86 | { 87 | settings.bODS = true; 88 | gbODS = true; 89 | } 90 | 91 | if(cmdParser.HasKey(L"lo")) 92 | { 93 | gLogPath = settings.localLogPath = cmdParser.GetVal(L"lo"); 94 | if(settings.localLogPath.IsEmpty()) 95 | Log(L"-lo missing value", true); 96 | } 97 | 98 | if (cmdParser.HasKey(L"share")) 99 | { 100 | settings.targetShare = cmdParser.GetVal(L"share"); 101 | if (settings.targetShare.IsEmpty()) 102 | Log(L"-share missing value", true); 103 | } 104 | 105 | #ifdef _DEBUG 106 | gbODS = true; 107 | #endif 108 | 109 | PrintCopyright(); 110 | 111 | #ifdef _DEBUG 112 | RegressionTests(); 113 | #endif 114 | 115 | if(ParseCommandLine(settings, ::GetCommandLine())) 116 | { 117 | if(gbStop) 118 | { 119 | exitCode = -11; 120 | goto Exit; 121 | } 122 | 123 | if(settings.computerList.empty()) 124 | { 125 | if(settings.bUseSystemAccount || ((DWORD)-1 != settings.sessionToInteractWith)) 126 | { 127 | //have to run service locally 128 | settings.computerList.push_back(L"."); 129 | } 130 | } 131 | 132 | if(settings.computerList.empty()) 133 | { 134 | //run locally 135 | bool b = StartProcess(settings, NULL); 136 | if(b) 137 | { 138 | if(false == settings.bDontWaitForTerminate) 139 | { 140 | DWORD waitMS = settings.timeoutSeconds * 1000; 141 | if(waitMS == 0) 142 | waitMS = INFINITE; 143 | DWORD ret = WaitForSingleObject(settings.hProcess, waitMS); 144 | switch(ret) 145 | { 146 | case WAIT_TIMEOUT: 147 | Log(L"PAExec timed out waiting for app to exit -- terminating app", true); 148 | TerminateProcess(settings.hProcess, (DWORD)-10); 149 | break; 150 | case WAIT_OBJECT_0: break; 151 | default: Log(L"PAExec error waiting for app to exit", GetLastError()); 152 | break; 153 | } 154 | GetExitCodeProcess(settings.hProcess, (DWORD*)&exitCode); 155 | } 156 | else 157 | Log(StrFormat(L"%s started with process ID %u", settings.app, settings.processID), false); 158 | } 159 | if(false == b) 160 | exitCode = -3; 161 | CloseHandle(settings.hProcess); 162 | } 163 | else 164 | { 165 | for(std::vector::iterator cItr = settings.computerList.begin(); (settings.computerList.end() != cItr) && (false == gbStop); cItr++) 166 | { 167 | HANDLE hPipe = INVALID_HANDLE_VALUE; 168 | 169 | settings.bNeedToDetachFromAdmin = false; 170 | settings.bNeedToDetachFromIPC = false; 171 | settings.bNeedToDeleteServiceFile = false; 172 | settings.bNeedToDeleteService = false; 173 | settings.hProcess = NULL; 174 | settings.processID = 0; 175 | settings.hStdErr = NULL; 176 | settings.hStdIn = NULL; 177 | settings.hStdOut = NULL; 178 | 179 | bool bNeedToSendFile = false; 180 | 181 | CString shownTargetName = *cItr; 182 | if(shownTargetName == L".") 183 | shownTargetName = L"{local server}"; 184 | 185 | Log(StrFormat(L"\r\nConnecting to %s...", shownTargetName), false); 186 | 187 | //ADMIN$ and IPC$ connections will be made as needed in CopyPAExecToRemote and InstallAndStartRemoteService 188 | 189 | __time64_t start = _time64(NULL); 190 | //copy myself 191 | if(false == CopyPAExecToRemote(settings, *cItr)) //logs on error 192 | { 193 | exitCode = -4; 194 | goto PerServerCleanup; 195 | } 196 | 197 | if(CheckTimeout(start, *cItr, settings)) 198 | { 199 | exitCode = -5; 200 | goto PerServerCleanup; 201 | } 202 | 203 | Log(StrFormat(L"Starting PAExec service on %s...", shownTargetName), false); 204 | //install myself as a remote service and start remote service 205 | if(false == InstallAndStartRemoteService(*cItr, settings)) //logs on error 206 | { 207 | exitCode = -6; 208 | goto PerServerCleanup; 209 | } 210 | 211 | settings.bNeedToDeleteService = true; //always try to clean up if we've gotten this far 212 | 213 | //Send settings, and find out if file(s) need to be copied 214 | if(false == SendSettings(*cItr, settings, hPipe, bNeedToSendFile)) 215 | { 216 | exitCode = -7; 217 | goto PerServerCleanup; 218 | } 219 | 220 | //copy target if needed 221 | if(bNeedToSendFile) 222 | { 223 | if(1 == settings.srcFileInfos.size()) 224 | Log(StrFormat(L"Copying %s remotely...", settings.srcFileInfos[0].filenameOnly), false); 225 | else 226 | Log(StrFormat(L"Copying %u files remotely...", settings.srcFileInfos.size()), false); 227 | 228 | if(false == SendFilesToRemote(*cItr, settings, hPipe)) 229 | { 230 | exitCode = -8; 231 | goto PerServerCleanup; 232 | } 233 | } 234 | 235 | Log(L"", false); //blank line 236 | 237 | //establish communication with remote service 238 | //when this returns, the remote app has shutdown (or we weren't supposed to wait for it) 239 | StartRemoteApp(*cItr, settings, hPipe, exitCode); 240 | 241 | PerServerCleanup: 242 | if(settings.bNeedToDeleteService) 243 | StopAndDeleteRemoteService(*cItr, settings); //always cleanup 244 | if(settings.bNeedToDeleteServiceFile) 245 | DeletePAExecFromRemote(*cItr, settings); 246 | if(settings.bNeedToDetachFromAdmin) 247 | EstablishConnection(settings, *cItr, settings.targetShare, false); 248 | if(settings.bNeedToDetachFromIPC) 249 | EstablishConnection(settings, *cItr, L"IPC$", false); 250 | if(false == gbStop) //if stopping, just bail -- the OS will close these (and some of them might be invalid now anyway) 251 | { 252 | if(!BAD_HANDLE(hPipe)) 253 | CloseHandle(hPipe); 254 | if(!BAD_HANDLE(settings.hProcess)) 255 | { 256 | CloseHandle(settings.hProcess); 257 | settings.hProcess = NULL; 258 | } 259 | if(!BAD_HANDLE(settings.hStdErr)) 260 | { 261 | CloseHandle(settings.hStdErr); 262 | settings.hStdErr = NULL; 263 | } 264 | if(!BAD_HANDLE(settings.hStdIn)) 265 | { 266 | CloseHandle(settings.hStdIn); 267 | settings.hStdIn = NULL; 268 | } 269 | if(!BAD_HANDLE(settings.hStdOut)) 270 | { 271 | CloseHandle(settings.hStdOut); 272 | settings.hStdOut = NULL; 273 | } 274 | if(!BAD_HANDLE(settings.hUserImpersonated)) 275 | { 276 | CloseHandle(settings.hUserImpersonated); 277 | settings.hUserImpersonated = NULL; 278 | } 279 | } 280 | } 281 | } 282 | } 283 | else 284 | { 285 | if(gbStop) 286 | { 287 | exitCode = -11; 288 | goto Exit; 289 | } 290 | 291 | PrintUsage(); 292 | bPrintExitCode = false; 293 | exitCode = -2; 294 | } 295 | 296 | //clean up 297 | if(!BAD_HANDLE(settings.hUserProfile)) 298 | { 299 | UnloadUserProfile(settings.hUser, settings.hUserProfile); 300 | settings.hUserProfile = NULL; 301 | } 302 | if(!BAD_HANDLE(settings.hUser)) 303 | { 304 | CloseHandle(settings.hUser); 305 | settings.hUser = NULL; 306 | } 307 | //} 308 | 309 | Exit: 310 | gbStop = true; 311 | 312 | if(bPrintExitCode) 313 | Log(StrFormat(L"\r\nPAExec returning exit code %d\r\n", exitCode), false); 314 | 315 | #ifdef _DEBUG 316 | WaitSomewhere(); 317 | #endif 318 | 319 | return exitCode; 320 | } 321 | 322 | void PrintCopyright() 323 | { 324 | CString ver; 325 | TCHAR filename[MAX_PATH * 4]; 326 | 327 | if(0 != GetModuleFileName(NULL, filename, MAX_PATH * 4)) 328 | { 329 | DWORD handle; 330 | DWORD verSize = GetFileVersionInfoSize(filename, &handle); 331 | BYTE* verInfo = new BYTE[verSize + 1]; 332 | if(GetFileVersionInfo(filename, NULL, verSize, verInfo)) 333 | { 334 | VS_FIXEDFILEINFO *fileInfo; 335 | UINT fileInfoSize; 336 | if(VerQueryValue(verInfo, TEXT("\\"), (void**)&fileInfo, &fileInfoSize)) 337 | ver = StrFormat(L"v%u.%u", ((WORD*)&(fileInfo->dwFileVersionMS))[1], ((WORD*)&(fileInfo->dwFileVersionMS))[0]); 338 | } 339 | 340 | delete [] verInfo; 341 | verInfo = NULL; 342 | } 343 | 344 | Log(StrFormat(L"\r\nPAExec %s - Execute Programs Remotely\r\nCopyright (c) 2012-2023 Power Admin LLC\r\nwww.poweradmin.com/PAExec\r\n", ver), false); 345 | } 346 | 347 | 348 | void PrintUsage() 349 | { 350 | HRSRC hR = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXT1), L"TEXT"); //OK 351 | 352 | DWORD size = SizeofResource(NULL, hR); 353 | HGLOBAL hG = LoadResource(NULL, hR); 354 | _ASSERT(NULL != hG); 355 | char* pCopy = new char[size + 1]; 356 | char* pB = (char*)LockResource(hG); 357 | memcpy(pCopy, pB, size); 358 | pCopy[size] = '\0'; 359 | printf("\r\n"); 360 | printf("%s",pCopy); 361 | printf("\r\n"); 362 | delete [] pCopy; 363 | } 364 | 365 | 366 | bool GetTargetFileInfo(FileInfo& fi) //returns whether all files were found or not 367 | { 368 | _ASSERT(FALSE == fi.fullFilePath.IsEmpty()); 369 | bool bAllFilesFound = true; 370 | 371 | HANDLE hSrcFile = INVALID_HANDLE_VALUE; 372 | hSrcFile = CreateFile(fi.fullFilePath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); 373 | DWORD gle = GetLastError(); 374 | 375 | if(BAD_HANDLE(hSrcFile)) 376 | { 377 | #ifdef _DEBUG 378 | Log(StrFormat(L"DEBUG: Failed to open file %s - can't get version info.", fi.fullFilePath), gle); 379 | #endif 380 | if(gbInService) 381 | { 382 | #ifdef _DEBUG 383 | Log(StrFormat(L"DEBUG: Marking to copy %s", fi.fullFilePath), false); 384 | #endif 385 | fi.bCopyFile = true; //file doesn't exist, so we need it to be copied 386 | } 387 | return false; 388 | } 389 | 390 | BY_HANDLE_FILE_INFORMATION fiInfo = {0}; 391 | if(FALSE == GetFileInformationByHandle(hSrcFile, &fiInfo)) 392 | { 393 | gle = GetLastError(); 394 | Log(StrFormat(L"Error getting file info from %s.", fi.fullFilePath), gle); 395 | CloseHandle(hSrcFile); 396 | return false; 397 | } 398 | 399 | fi.fileLastWrite = fiInfo.ftLastWriteTime; 400 | CloseHandle(hSrcFile); 401 | hSrcFile = INVALID_HANDLE_VALUE; 402 | 403 | DWORD handle; 404 | DWORD verSize = GetFileVersionInfoSize(fi.fullFilePath, &handle); 405 | BYTE* verInfo = new BYTE[verSize + 1]; 406 | if(GetFileVersionInfo(fi.fullFilePath, NULL, verSize, verInfo)) 407 | { 408 | VS_FIXEDFILEINFO *fileInfo = NULL; 409 | UINT fileInfoSize = 0; 410 | if(VerQueryValue(verInfo, TEXT("\\"), (void**)&fileInfo, &fileInfoSize)) 411 | { 412 | fi.fileVersionLS = fileInfo->dwFileVersionLS; 413 | fi.fileVersionMS = fileInfo->dwFileDateMS; 414 | } 415 | } 416 | delete [] verInfo; 417 | return true; //got file info 418 | } 419 | 420 | void RegressionTests() 421 | { 422 | Log(L" --- start regression tests --- ", false); 423 | 424 | { 425 | Settings settings; 426 | _ASSERT(false == ParseCommandLine(settings, L"/?")); 427 | } 428 | 429 | { 430 | Settings settings; 431 | //dumb example (-c and cmd.exe) but still need to figure out how to handle it 432 | _ASSERT(ParseCommandLine(settings, L"C:\\PAExec.exe -u doug -p test -c -w \"C:\\Windows\\system32\" cmd.exe")); 433 | 434 | RemMsg msg1; 435 | settings.Serialize(msg1, true); 436 | msg1.m_bResetReadItr = true; 437 | settings.Serialize(msg1, false); 438 | 439 | _ASSERT(settings.bCopyFiles); _ASSERT(settings.workingDir == L"C:\\Windows\\system32"); _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 440 | _ASSERT(settings.user == L"doug"); _ASSERT(settings.password == L"test"); 441 | } 442 | 443 | { 444 | Settings settings; 445 | _ASSERT(ParseCommandLine(settings, L"C:\\PAExec.exe \\\\192.168.7.2 -i calc")); //make sure -i doesn't eat calc 446 | 447 | _ASSERT(0 == settings.app.CompareNoCase(L"calc")); 448 | } 449 | 450 | { 451 | Settings settings1, settings2; 452 | _ASSERT(ParseCommandLine(settings1, L"C:\\PAExec.exe \\\\192.168.7.2 -i calc")); //make sure -i doesn't eat calc 453 | _ASSERT(ParseCommandLine(settings2, L"C:\\PAExec.exe \\\\192.168.7.2 calc")); //make sure -i doesn't eat calc 454 | 455 | _ASSERT(settings1.app == settings2.app); 456 | } 457 | 458 | { 459 | Settings settings; 460 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -u doug -p test -c -w \"C:\\Windows space\\system32\" cmd.exe")); 461 | 462 | RemMsg msg1; 463 | settings.Serialize(msg1, true); 464 | msg1.m_bResetReadItr = true; 465 | settings.Serialize(msg1, false); 466 | 467 | _ASSERT(settings.bCopyFiles); _ASSERT(settings.workingDir == L"C:\\Windows space\\system32"); _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 468 | _ASSERT(settings.user == L"doug"); _ASSERT(settings.password == L"test"); 469 | } 470 | 471 | { 472 | Settings settings; 473 | _ASSERT(false == ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" /?")); 474 | } 475 | 476 | { 477 | Settings settings; 478 | _ASSERT(ParseCommandLine(settings, L"\\\\remote_server taskmgr.exe /?")); 479 | _ASSERT(settings.appArgs == L"/?"); 480 | _ASSERT(settings.app == L"taskmgr.exe"); 481 | } 482 | 483 | { 484 | Settings settings; 485 | _ASSERT(ParseCommandLine(settings, L"\\\\remote_server taskmgr.exe /c")); 486 | _ASSERT(settings.appArgs == L"/c"); 487 | _ASSERT(settings.app == L"taskmgr.exe"); 488 | } 489 | 490 | { 491 | Settings settings; 492 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -a 1,2 -d -s cmd.exe")); 493 | 494 | RemMsg msg1; 495 | settings.Serialize(msg1, true); 496 | msg1.m_bResetReadItr = true; 497 | settings.Serialize(msg1, false); 498 | 499 | _ASSERT(settings.bDontWaitForTerminate); _ASSERT(settings.allowedProcessors.size() == 2); _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 500 | } 501 | 502 | { 503 | Settings settings; 504 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -a 1,2 -d -u Doug -p Test cmd.exe ping me")); 505 | 506 | RemMsg msg1; 507 | settings.Serialize(msg1, true); 508 | msg1.m_bResetReadItr = true; 509 | settings.Serialize(msg1, false); 510 | 511 | _ASSERT(settings.bDontWaitForTerminate); 512 | _ASSERT(settings.allowedProcessors.size() == 2); 513 | _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 514 | _ASSERT(settings.appArgs == L"ping me"); 515 | } 516 | 517 | { 518 | Settings settings; 519 | //bad path, but -c not specified so we won't catch it at parse time 520 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -s \"C:\\path with\\space \\cmd.exe\" ping me")); 521 | 522 | RemMsg msg1; 523 | settings.Serialize(msg1, true); 524 | msg1.m_bResetReadItr = true; 525 | settings.Serialize(msg1, false); 526 | 527 | _ASSERT(settings.app == L"C:\\path with\\space \\cmd.exe"); _ASSERT(settings.appArgs == L"ping me"); 528 | } 529 | 530 | { 531 | Settings settings; 532 | //bad path 533 | _ASSERT(false == ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -c -v -s \"C:\\path with\\space \\cmd.exe\"")); 534 | 535 | RemMsg msg1; 536 | settings.Serialize(msg1, true); 537 | msg1.m_bResetReadItr = true; 538 | settings.Serialize(msg1, false); 539 | 540 | _ASSERT(settings.bCopyFiles); 541 | _ASSERT(settings.bCopyIfNewerOrHigherVer); 542 | _ASSERT(settings.app == L"C:\\path with\\space \\cmd.exe"); 543 | _ASSERT(settings.appArgs.IsEmpty()); 544 | } 545 | 546 | { 547 | Settings settings; 548 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -u Doug -P TEST -c -v cmd.exe")); 549 | 550 | _ASSERT(settings.bCopyFiles); 551 | _ASSERT(settings.bCopyIfNewerOrHigherVer); 552 | _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 553 | _ASSERT(settings.appArgs.IsEmpty()); 554 | 555 | RemMsg msg1; 556 | settings.Serialize(msg1, true); 557 | msg1.m_bResetReadItr = true; 558 | settings.Serialize(msg1, false); 559 | 560 | _ASSERT(settings.bCopyFiles); 561 | _ASSERT(settings.bCopyIfNewerOrHigherVer); 562 | _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 563 | _ASSERT(settings.appArgs.IsEmpty()); 564 | } 565 | 566 | { 567 | Settings settings; 568 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -csrc C:\\Windows\\notepad.exe -c cmd.exe")); 569 | 570 | _ASSERT(settings.bCopyFiles); _ASSERT(false == settings.bCopyIfNewerOrHigherVer); _ASSERT(0 == settings.app.CompareNoCase(L"cmd.exe")); 571 | _ASSERT(settings.appArgs.IsEmpty()); 572 | _ASSERT(0 == settings.srcFileInfos[0].fullFilePath.CompareNoCase(L"C:\\windows\\notepad.exe")); 573 | _ASSERT(0 == settings.destFileInfos[0].filenameOnly.CompareNoCase(L"cmd.exe")); 574 | _ASSERT(settings.destFileInfos[0].fullFilePath.IsEmpty()); 575 | } 576 | 577 | { 578 | Settings settings; 579 | //false because all the files in regression1 can't be found 580 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -clist debug\\regression1.txt -c myapp.exe")); 581 | 582 | _ASSERT(settings.bCopyFiles); _ASSERT(false == settings.bCopyIfNewerOrHigherVer); _ASSERT(0 == settings.app.CompareNoCase(L"myapp.exe")); 583 | _ASSERT(settings.appArgs.IsEmpty()); 584 | 585 | _ASSERT(0 == settings.srcFileInfos[0].filenameOnly.CompareNoCase(L"paexec.exe")); //using paexec.exe so it is found in the debug folder 586 | _ASSERT(0 == settings.srcFileInfos[1].filenameOnly.CompareNoCase(L"paexec.obj")); 587 | _ASSERT(0 == settings.srcFileInfos[2].filenameOnly.CompareNoCase(L"paexec.pdb")); 588 | 589 | _ASSERT(0 == settings.destFileInfos[0].filenameOnly.CompareNoCase(L"myapp.exe")); //file name changed in dest based on settings.app 590 | _ASSERT(0 == settings.destFileInfos[1].filenameOnly.CompareNoCase(L"paexec.obj")); 591 | _ASSERT(0 == settings.destFileInfos[2].filenameOnly.CompareNoCase(L"paexec.pdb")); 592 | 593 | _ASSERT(FALSE == settings.srcFileInfos[0].fullFilePath.IsEmpty()); 594 | _ASSERT(FALSE == settings.srcFileInfos[1].fullFilePath.IsEmpty()); 595 | _ASSERT(FALSE == settings.srcFileInfos[2].fullFilePath.IsEmpty()); 596 | 597 | _ASSERT(FALSE == settings.srcDir.IsEmpty()); 598 | 599 | _ASSERT(0 == settings.destFileInfos[0].filenameOnly.CompareNoCase(L"myapp.exe")); 600 | 601 | _ASSERT(settings.destFileInfos.size() == settings.srcFileInfos.size()); 602 | } 603 | 604 | { 605 | Settings settings; 606 | _ASSERT(ParseCommandLine(settings, L"\"C:\\dir with space\\PAExec.exe\" -clist debug\\regression2.txt -c myapp.exe")); 607 | 608 | _ASSERT(settings.bCopyFiles); _ASSERT(false == settings.bCopyIfNewerOrHigherVer); _ASSERT(0 == settings.app.CompareNoCase(L"myapp.exe")); 609 | _ASSERT(settings.appArgs.IsEmpty()); 610 | 611 | _ASSERT(0 == settings.srcFileInfos[0].filenameOnly.CompareNoCase(L"paexec.exe")); 612 | _ASSERT(0 == settings.destFileInfos[0].filenameOnly.CompareNoCase(L"myapp.exe")); //dest using different filename based on settings.app 613 | 614 | _ASSERT(FALSE == settings.srcFileInfos[0].fullFilePath.IsEmpty()); 615 | 616 | _ASSERT(FALSE == settings.srcDir.IsEmpty()); 617 | 618 | _ASSERT(settings.destFileInfos.size() == settings.srcFileInfos.size()); 619 | } 620 | 621 | { 622 | Settings settings; 623 | 624 | _ASSERT(ParseCommandLine(settings, L"\\dell8 C:\\Windows\\System32\\wevtutil.exe qe System /q:*^[System^[^(EventID=6008^)^]^] /f:text /c:1 /rd:true > c:\\temp\\tmpfile.txt 2>NUL")); 625 | 626 | _ASSERT(false == settings.bCopyFiles); //ensure /c in the wevtutil.exe command line is not seen by PAExec's command line parsing 627 | 628 | _ASSERT(false == settings.bCopyIfNewerOrHigherVer); 629 | _ASSERT(0 == settings.app.CompareNoCase(L"C:\\Windows\\System32\\wevtutil.exe")); 630 | _ASSERT(FALSE == settings.appArgs.IsEmpty()); 631 | 632 | _ASSERT(settings.destFileInfos.size() == settings.srcFileInfos.size()); 633 | } 634 | 635 | { 636 | Settings settings; 637 | 638 | _ASSERT(ParseCommandLine(settings, L"\\\\dell8 C:\\Windows\\System32\\wevtutil.exe qe System /q:*^[System^[^(EventID=6008^)^]^] /f:text -c:1 /rd:true > c:\\temp\\tmpfile.txt 2>NUL")); 639 | 640 | _ASSERT(false == settings.bCopyFiles); //ensure -c in the wevtutil.exe command line is not seen by PAExec's command line parsing 641 | 642 | _ASSERT(false == settings.bCopyIfNewerOrHigherVer); 643 | _ASSERT(0 == settings.app.CompareNoCase(L"C:\\Windows\\System32\\wevtutil.exe")); 644 | _ASSERT(FALSE == settings.appArgs.IsEmpty()); 645 | 646 | _ASSERT(settings.destFileInfos.size() == settings.srcFileInfos.size()); 647 | } 648 | 649 | { 650 | Settings settings; 651 | 652 | _ASSERT(ParseCommandLine(settings, L"\\\\dell8 CSCRIPT C:\\Windows\\System32\\eventquery.vbs /fi \"id eq 6008\" /l system 2^>NUL ^| find \"EventLog\"")); 653 | 654 | _ASSERT(false == settings.bCopyFiles); 655 | 656 | _ASSERT(false == settings.bCopyIfNewerOrHigherVer); 657 | _ASSERT(0 == settings.app.CompareNoCase(L"CSCRIPT")); 658 | _ASSERT(FALSE == settings.appArgs.IsEmpty()); 659 | 660 | _ASSERT(settings.destFileInfos.size() == settings.srcFileInfos.size()); 661 | } 662 | 663 | { 664 | Settings settings; 665 | 666 | _ASSERT(ParseCommandLine(settings, L"\\\\dell8 -c -cnodel notepad.exe")); 667 | } 668 | 669 | 670 | Log(L" --- end regression tests --- \r\n", false); 671 | } 672 | 673 | 674 | BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) 675 | { 676 | switch( dwCtrlType ) 677 | { 678 | case CTRL_C_EVENT: 679 | case CTRL_BREAK_EVENT: 680 | case CTRL_CLOSE_EVENT: 681 | case CTRL_LOGOFF_EVENT: 682 | case CTRL_SHUTDOWN_EVENT: 683 | printf("^C (stopping)\r\n"); 684 | gbStop = true; 685 | return TRUE; 686 | } 687 | 688 | return FALSE; 689 | } 690 | 691 | 692 | bool CheckTimeout(__time64_t start, LPCWSTR serverName, Settings& settings) 693 | { 694 | if((0 != settings.remoteCompConnectTimeoutSec) && ((_time64(NULL) - start) > settings.remoteCompConnectTimeoutSec)) 695 | { 696 | Log(StrFormat(L"Timeout connecting to %s...", serverName), true); 697 | return true; 698 | } 699 | return false; 700 | } 701 | 702 | 703 | 704 | 705 | bool Settings::ResolveFilePaths() 706 | { 707 | bool bAllFilesFound = true; 708 | 709 | //dir may or may not be set. if not set, grab it from first file (which will have to be found on the path) 710 | _ASSERT(false == srcFileInfos.empty()); 711 | _ASSERT(false == destFileInfos.empty()); 712 | 713 | CString* pDir = &srcDir; 714 | std::vector* pFileList = &srcFileInfos; 715 | 716 | if(gbInService) 717 | { 718 | #ifdef _DEBUG 719 | Log(L"DEBUG: ResolveFilePaths in service", false); 720 | #endif 721 | pDir = &destDir; 722 | pFileList = &destFileInfos; 723 | } 724 | 725 | std::vector results; 726 | for(std::vector::iterator itr = pFileList->begin(); pFileList->end() != itr; itr++) 727 | { 728 | CString path = *pDir; 729 | if((FALSE == path.IsEmpty()) && (path.Right(1) != L"\\")) 730 | path += L"\\"; 731 | path += (*itr).filenameOnly; 732 | 733 | wchar_t expanded[_MAX_PATH * 4] = {0}; 734 | ExpandEnvironmentStrings(path, expanded, sizeof(expanded)/sizeof(wchar_t)); 735 | if(0 != wcslen(expanded)) 736 | path = expanded; 737 | 738 | if(0 != _waccess(path, 0)) 739 | { 740 | path = ExpandToFullPath(path); //search on path if no path specified 741 | 742 | if(0 != _waccess(path, 0)) 743 | bAllFilesFound = false; 744 | } 745 | 746 | if(pFileList->begin() == itr) 747 | { 748 | if((false == bAllFilesFound) && (false == gbInService)) 749 | return false; //can't find the very first (executable target) file, so bail 750 | 751 | if(pDir->IsEmpty()) 752 | { 753 | //figure out source/target dir based on this first file 754 | LPWSTR cPtr = (LPWSTR)wcsrchr(path, L'\\'); 755 | if(NULL != cPtr) 756 | { 757 | *cPtr = L'\0'; //truncate path 758 | *pDir = (LPCWSTR)path; 759 | *cPtr = L'\\'; //restore 760 | } 761 | else 762 | { 763 | //file must not exist, so assume windows directory 764 | GetWindowsDirectory(pDir->GetBuffer(_MAX_PATH), _MAX_PATH); 765 | pDir->ReleaseBuffer(); 766 | //and now repair path 767 | path = *pDir; 768 | if((FALSE == path.IsEmpty()) && (path.Right(1) != L"\\")) 769 | path += L"\\"; 770 | path += (*itr).filenameOnly; 771 | } 772 | } 773 | } 774 | 775 | (*itr).fullFilePath = path; 776 | 777 | #ifdef _DEBUG 778 | Log(StrFormat(L"DEBUG: Setting full path to %s", path), false); 779 | #endif 780 | 781 | bAllFilesFound &= GetTargetFileInfo(*itr); //non-existent files in the service will get marked as bCopy = true 782 | } 783 | 784 | _ASSERT(FALSE == pDir->IsEmpty()); 785 | 786 | return bAllFilesFound; 787 | } 788 | -------------------------------------------------------------------------------- /PAExec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "resource.h" 4 | 5 | #ifdef _DEBUG 6 | 7 | #undef _ASSERT 8 | #define _ASSERT(expr) \ 9 | (void) ((!!(expr)) || \ 10 | (ReportAssert(_CRT_WIDE(__FILE__), __LINE__, _CRT_WIDE(#expr))) || \ 11 | (_CrtDbgBreak(), 0)) 12 | 13 | extern bool ReportAssert(LPCWSTR file, int line, LPCWSTR expr); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /PAExec.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "afxres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (United States) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 20 | #pragma code_page(1252) 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""afxres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Version 51 | // 52 | 53 | VS_VERSION_INFO VERSIONINFO 54 | FILEVERSION 1,31,0,0 55 | PRODUCTVERSION 1,31,0,0 56 | FILEFLAGSMASK 0x17L 57 | #ifdef _DEBUG 58 | FILEFLAGS 0x1L 59 | #else 60 | FILEFLAGS 0x0L 61 | #endif 62 | FILEOS 0x4L 63 | FILETYPE 0x1L 64 | FILESUBTYPE 0x0L 65 | BEGIN 66 | BLOCK "StringFileInfo" 67 | BEGIN 68 | BLOCK "040904b0" 69 | BEGIN 70 | VALUE "CompanyName", "Power Admin LLC" 71 | VALUE "FileDescription", "PAExec Application" 72 | VALUE "FileVersion", "1.31.0.0" 73 | VALUE "InternalName", "PAExec" 74 | VALUE "LegalCopyright", "Copyright (c) 2012-2025 Power Admin LLC" 75 | VALUE "OriginalFilename", "PAExec.exe" 76 | VALUE "ProductName", "PAExec Application" 77 | VALUE "ProductVersion", "1.31.0.0" 78 | END 79 | END 80 | BLOCK "VarFileInfo" 81 | BEGIN 82 | VALUE "Translation", 0x409, 1200 83 | END 84 | END 85 | 86 | 87 | ///////////////////////////////////////////////////////////////////////////// 88 | // 89 | // TEXT 90 | // 91 | 92 | IDR_TEXT1 TEXT "Usage.txt" 93 | 94 | 95 | ///////////////////////////////////////////////////////////////////////////// 96 | // 97 | // String Table 98 | // 99 | 100 | STRINGTABLE 101 | BEGIN 102 | IDS_APP_TITLE "PAExec" 103 | END 104 | 105 | #endif // English (United States) resources 106 | ///////////////////////////////////////////////////////////////////////////// 107 | 108 | 109 | 110 | #ifndef APSTUDIO_INVOKED 111 | ///////////////////////////////////////////////////////////////////////////// 112 | // 113 | // Generated from the TEXTINCLUDE 3 resource. 114 | // 115 | 116 | 117 | ///////////////////////////////////////////////////////////////////////////// 118 | #endif // not APSTUDIO_INVOKED 119 | 120 | -------------------------------------------------------------------------------- /PAExec.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PAExec", "PAExec.vcxproj", "{2FEB96F5-08E6-48A3-B306-794277650A08}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {2FEB96F5-08E6-48A3-B306-794277650A08}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {2FEB96F5-08E6-48A3-B306-794277650A08}.Debug|Win32.Build.0 = Debug|Win32 14 | {2FEB96F5-08E6-48A3-B306-794277650A08}.Release|Win32.ActiveCfg = Release|Win32 15 | {2FEB96F5-08E6-48A3-B306-794277650A08}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /PAExec.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 27 | 30 | 33 | 36 | 39 | 42 | 53 | 56 | 59 | 62 | 71 | 74 | 77 | 80 | 83 | 86 | 89 | 93 | 94 | 103 | 106 | 109 | 112 | 115 | 118 | 129 | 132 | 135 | 138 | 148 | 151 | 154 | 157 | 160 | 163 | 166 | 171 | 172 | 173 | 174 | 175 | 176 | 181 | 184 | 185 | 188 | 189 | 192 | 193 | 196 | 197 | 200 | 201 | 204 | 205 | 208 | 209 | 212 | 213 | 216 | 219 | 223 | 224 | 227 | 231 | 232 | 233 | 236 | 237 | 238 | 243 | 246 | 247 | 250 | 251 | 254 | 255 | 258 | 259 | 260 | 265 | 268 | 269 | 270 | 273 | 274 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /PAExec.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {2FEB96F5-08E6-48A3-B306-794277650A08} 15 | PAExec 16 | Win32Proj 17 | 10.0 18 | 19 | 20 | 21 | Application 22 | v143 23 | false 24 | Unicode 25 | true 26 | 27 | 28 | Application 29 | v143 30 | false 31 | Unicode 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <_ProjectFileVersion>11.0.60610.1 45 | 46 | 47 | $(SolutionDir)$(Configuration)\ 48 | $(Configuration)\ 49 | true 50 | 51 | 52 | $(SolutionDir)$(Configuration)\ 53 | $(Configuration)\ 54 | false 55 | 56 | 57 | 58 | Disabled 59 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 60 | true 61 | EnableFastChecks 62 | MultiThreadedDebug 63 | Use 64 | Level3 65 | EditAndContinue 66 | 67 | 68 | version.lib;Netapi32.lib;userenv.lib;Psapi.lib;WtsApi32.lib;mpr.lib;advapi32.lib;%(AdditionalDependencies) 69 | AsInvoker 70 | true 71 | Console 72 | MachineX86 73 | 5.01 74 | 75 | 76 | copy regression*.txt $(TargetDir) 77 | 78 | 79 | 80 | 81 | MinSpace 82 | true 83 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 84 | MultiThreaded 85 | true 86 | Use 87 | Level3 88 | ProgramDatabase 89 | Size 90 | 91 | 92 | version.lib;Netapi32.lib;userenv.lib;Psapi.lib;WtsApi32.lib;mpr.lib;advapi32.lib;%(AdditionalDependencies) 93 | true 94 | Console 95 | true 96 | true 97 | MachineX86 98 | false 99 | winmm.lib;uafxcw.lib 100 | 5.01 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Create 118 | Create 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | true 134 | true 135 | 136 | 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /PAExec.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | 64 | 65 | Resource Files 66 | 67 | 68 | 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | -------------------------------------------------------------------------------- /Parsing.cpp: -------------------------------------------------------------------------------- 1 | // Parsing.cpp: Commandline parsing and interpretting 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | #include "stdafx.h" 11 | #include "PAExec.h" 12 | #include "CmdLineParser.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef _DEBUG 20 | #define new DEBUG_NEW 21 | #undef THIS_FILE 22 | static char THIS_FILE[] = __FILE__; 23 | #endif 24 | 25 | 26 | bool GetComputerList(Settings& settings, LPCWSTR& cmdLine) 27 | { 28 | DWORD gle = 0; 29 | 30 | // [\\computer[,computer2[,...] | @file]] 31 | //@file PsExec will execute the command on each of the computers listed in the file. 32 | //a computer name of "\\*" PsExec runs the applications on all computers in the current domain. 33 | 34 | //first part of command line is the executable itself, so skip past that 35 | if(L'"' == *cmdLine) 36 | { 37 | //path is quoted, so skip to end quote 38 | cmdLine = wcschr(cmdLine + 1, L'"'); 39 | if(NULL != cmdLine) 40 | cmdLine++; 41 | } 42 | else 43 | { 44 | //no quotes, skip forward to whitespace 45 | while(!iswspace(*cmdLine) && *cmdLine) 46 | cmdLine++; 47 | } 48 | 49 | //now skip past white space 50 | while(iswspace(*cmdLine) && *cmdLine) 51 | cmdLine++; 52 | 53 | //if we see -accepteula or /accepteula, skip over it 54 | if( (0 == _wcsnicmp(cmdLine, L"/accepteula", 11)) || (0 == _wcsnicmp(cmdLine, L"-accepteula", 11)) ) 55 | { 56 | cmdLine += 11; //get past Eula 57 | //now skip past white space 58 | while(iswspace(*cmdLine) && *cmdLine) 59 | cmdLine++; 60 | } 61 | 62 | if(0 == wcsncmp(cmdLine, L"\\\\*", 3)) 63 | { 64 | cmdLine += 3; 65 | //get server list from domain 66 | SERVER_INFO_100* pInfo = NULL; 67 | DWORD numServers = 0, total = 0; 68 | DWORD ignored = 0; 69 | NET_API_STATUS stat = NetServerEnum(NULL, 100, (LPBYTE*)&pInfo, MAX_PREFERRED_LENGTH, &numServers, &total, SV_TYPE_SERVER | SV_TYPE_WINDOWS, NULL, &ignored); 70 | if(NERR_Success == stat) 71 | { 72 | for(DWORD i = 0; i < numServers; i++) 73 | settings.computerList.push_back(pInfo[i].sv100_name); 74 | } 75 | else 76 | Log(L"Got error from NetServerEnum: ", (DWORD)stat); 77 | NetApiBufferFree(pInfo); 78 | pInfo = NULL; 79 | if(settings.computerList.empty()) 80 | Log(L"No computers could be found", true); 81 | return !settings.computerList.empty(); 82 | } 83 | else 84 | { 85 | if(L'@' == *cmdLine) 86 | { 87 | //read server list from file. Assumes UTF8 88 | LPCWSTR fileStart = cmdLine + 1; 89 | while(!iswspace(*cmdLine) && *cmdLine) 90 | cmdLine++; 91 | CString file = CString(fileStart).Left(cmdLine - fileStart); 92 | 93 | file = ExpandToFullPath(file); //search on path if no path specified 94 | 95 | CString content; 96 | if(false == ReadTextFile(file, content)) 97 | return false; 98 | 99 | wchar_t* pC = wcstok(content.LockBuffer(), L"\r\n"); 100 | while(NULL != pC) 101 | { 102 | CString s = pC; 103 | s.Trim(); 104 | if(FALSE == s.IsEmpty()) 105 | settings.computerList.push_back(s); 106 | pC = wcstok(NULL, L"\r\n"); 107 | } 108 | if(settings.computerList.empty()) 109 | Log(L"Computer list file empty", true); 110 | return !settings.computerList.empty(); 111 | } 112 | else 113 | { 114 | if(0 == wcsncmp(cmdLine, L"\\\\", 2)) 115 | { 116 | //get, possibly comma-delimited, computer list 117 | LPCWSTR compListStart = cmdLine + 2; 118 | //skip forward to whitespace 119 | while(!iswspace(*cmdLine) && *cmdLine) 120 | cmdLine++; 121 | CString compList = CString(compListStart).Left(cmdLine - compListStart); 122 | wchar_t* pC = wcstok(compList.LockBuffer(), L","); 123 | while(NULL != pC) 124 | { 125 | CString s = pC; 126 | s.Trim(); 127 | if(FALSE == s.IsEmpty()) 128 | settings.computerList.push_back(s); 129 | pC = wcstok(NULL, L","); 130 | } 131 | 132 | if(settings.computerList.empty()) 133 | Log(L"Computer not specified", true); 134 | return !settings.computerList.empty(); 135 | } 136 | return true; //no server specified 137 | } 138 | } 139 | } 140 | 141 | LPCWSTR SkipForward(LPCWSTR cmdLinePastCompList, LPCWSTR arg, bool bCanHaveArg) 142 | { 143 | LPCWSTR pStart = cmdLinePastCompList; 144 | Top: 145 | LPCWSTR pC = wcsstr(pStart, arg); 146 | if(NULL != pC) 147 | { 148 | pC += wcslen(arg); 149 | if(false == iswspace(*pC)) 150 | { 151 | //we're in a larger delimiter, so skip past this 152 | pStart = pC + 1; 153 | goto Top; 154 | } 155 | 156 | while(iswspace(*pC) && *pC) 157 | pC++; 158 | if(bCanHaveArg) 159 | if(*pC != L'-') 160 | { 161 | bool bInQuote = false; 162 | while((!iswspace(*pC) || (*pC == L'"') || bInQuote) && *pC) 163 | { 164 | if(*pC == L'"') 165 | bInQuote = !bInQuote; 166 | pC++; 167 | } 168 | //to end of arg now 169 | while(iswspace(*pC) && *pC) 170 | pC++; 171 | } 172 | if(pC > cmdLinePastCompList) 173 | cmdLinePastCompList = pC; 174 | } 175 | return cmdLinePastCompList; 176 | } 177 | 178 | typedef struct 179 | { 180 | LPCWSTR cmd; 181 | bool bCanHaveArgs; 182 | bool bMustHaveArgs; 183 | }CommandList; 184 | 185 | CommandList gSupportedCommands[] = 186 | { 187 | {L"u", true, true}, 188 | {L"p", true, false}, 189 | {L"p@", true, true}, 190 | {L"p@d", false, false}, 191 | {L"n", true, true}, 192 | {L"l", false, false}, 193 | {L"h", false, false}, 194 | {L"s", false, false}, 195 | {L"e", false, false}, 196 | {L"x", false, false}, 197 | {L"i", true, false}, 198 | {L"c", false, false}, 199 | {L"cnodel", false, false}, 200 | {L"f", false, false}, 201 | {L"v", false, false}, 202 | {L"w", true, true}, 203 | {L"d", false, false}, 204 | {L"low", false, false}, 205 | {L"belownormal", false, false}, 206 | {L"abovenormal", false, false}, 207 | {L"high", false, false}, 208 | {L"realtime", false, false}, 209 | {L"background", false, false}, 210 | {L"a", true, true}, 211 | {L"csrc", true, true}, 212 | {L"clist", true, true}, 213 | {L"dfr", false, false}, 214 | {L"lo", true, true}, 215 | {L"rlo", true, true}, 216 | {L"dbg", false, false}, 217 | {L"to", true, true}, 218 | {L"noname", false, false}, 219 | {L"sname", true, true}, 220 | {L"share", true, true}, 221 | {L"sharepath", true, true}, 222 | {L"accepteula", false, false} //non-documented PSExec command that we'll just silently eat 223 | }; 224 | 225 | LPCWSTR EatWhiteSpace(LPCWSTR ptr) 226 | { 227 | while(iswspace(*ptr) && *ptr) 228 | ptr++; 229 | 230 | return ptr; 231 | } 232 | 233 | bool SplitCommand(CString& restOfLine, LPCWSTR& paExecParams, LPCWSTR& appToRun) 234 | { 235 | LPWSTR ptr = (LPWSTR)(LPCWSTR)restOfLine; 236 | while(true) 237 | { 238 | if((L'-' == *ptr) || (L'/' == *ptr)) 239 | { 240 | if(NULL == paExecParams) 241 | paExecParams = ptr; 242 | 243 | ptr++; 244 | LPCWSTR startOfCmd = ptr; 245 | //skip to end of alpha chars which will signal the end of this command 246 | while( (iswalpha(*ptr) || (L'@' == *ptr)) && *ptr) 247 | ptr++; 248 | if(L'\0' == *ptr) 249 | { 250 | Log(L"Reached end of command before seeing expected parts", true); 251 | return false; 252 | } 253 | 254 | //ptr is beyond end of cmd 255 | size_t len = ptr - startOfCmd; 256 | 257 | bool bRecognized = false; 258 | //now see if this is a recognized command 259 | int i = 0; 260 | for(i = 0; i < sizeof(gSupportedCommands)/sizeof(gSupportedCommands[0]); i++) 261 | { 262 | if(wcslen(gSupportedCommands[i].cmd) != len) 263 | continue; 264 | CString lwrCmd = startOfCmd; 265 | lwrCmd.MakeLower(); 266 | if(0 == wcsncmp(lwrCmd, gSupportedCommands[i].cmd, len)) 267 | { 268 | bRecognized = true; 269 | break; 270 | } 271 | } 272 | 273 | if(false == bRecognized) 274 | { 275 | *ptr = L'\0'; 276 | Log(StrFormat(L"%s is not a recognized option", startOfCmd), true); 277 | _ASSERT(0); 278 | return false; 279 | } 280 | 281 | ptr = (LPWSTR)EatWhiteSpace(ptr); 282 | if(L'\0' == *ptr) 283 | { 284 | Log(L"Reached end of command before seeing expected parts", true); 285 | _ASSERT(0); 286 | return false; 287 | } 288 | 289 | if(gSupportedCommands[i].bCanHaveArgs) 290 | { 291 | if( (L'-' != *ptr) && (L'/' != *ptr)) 292 | { 293 | //special handling for -i which may or may not have an argument, but if it does, it is numeric 294 | if(0 == wcscmp(gSupportedCommands[i].cmd, L"i")) 295 | { 296 | //wtodw(' ') and wtodw('0') both return 0 297 | if(L'0' != *ptr) 298 | { 299 | if(0 == wtodw(ptr)) 300 | continue; //no argument 301 | } 302 | } 303 | 304 | bool bInQuote = false; 305 | while((!iswspace(*ptr) || (*ptr == L'"') || bInQuote) && *ptr) 306 | { 307 | if(*ptr == L'"') 308 | bInQuote = !bInQuote; 309 | ptr++; 310 | } 311 | //to end of arg now 312 | ptr = (LPWSTR)EatWhiteSpace(ptr); 313 | if((L'\0' == *ptr) && (gSupportedCommands[i].bMustHaveArgs)) 314 | { 315 | Log(L"Reached end of command before seeing expected parts", true); 316 | _ASSERT(0); 317 | return false; 318 | } 319 | } 320 | } 321 | } 322 | else 323 | { 324 | //must have found the start of the app 325 | ptr[-1] = L'\0'; //terminate PAExec part 326 | appToRun = ptr; 327 | return true; 328 | } 329 | } 330 | } 331 | 332 | bool ParseCommandLine(Settings& settings, LPCWSTR cmdLine) 333 | { 334 | //Parse command line options into settings 335 | LPCWSTR cmdLinePastCompList = cmdLine; 336 | if(GetComputerList(settings, cmdLinePastCompList)) 337 | { 338 | cmdLinePastCompList = EatWhiteSpace(cmdLinePastCompList); 339 | 340 | if(L'\0' == *cmdLinePastCompList) 341 | return false; //empty command line -- just show usage 342 | 343 | if((0 == wcsncmp(cmdLinePastCompList, L"/?", 2)) || (0 == wcsncmp(cmdLinePastCompList, L"-?", 2))) 344 | return false; //show usage 345 | 346 | //sitting on first character that could be program to run, or arguments. Need to split the 347 | //rest of the string in half -- half for PAExec arguments, and the other half for the program 348 | //to run (and perhaps it's arguments as well) 349 | CString restOfLine = cmdLinePastCompList; 350 | LPCWSTR paExecParams = NULL; 351 | LPCWSTR pAppStart = NULL; 352 | if(false == SplitCommand(restOfLine, paExecParams, pAppStart)) 353 | return false; 354 | 355 | CCmdLineParser cmdParser; 356 | //re-parse now that funky stuff has been skipped over 357 | cmdParser.Parse(paExecParams); 358 | 359 | if(cmdParser.HasKey(L"a")) 360 | { 361 | CString procList = cmdParser.GetVal(L"a"); 362 | LPCWSTR pC = wcstok(procList.LockBuffer(), L","); 363 | while(NULL != pC) 364 | { 365 | DWORD num = wtodw(pC); 366 | if(0 != num) 367 | settings.allowedProcessors.push_back((WORD)num); 368 | pC = wcstok(NULL, L","); 369 | } 370 | if(settings.allowedProcessors.empty()) 371 | { 372 | Log(L"-a specified without non-zero values", true); 373 | return false; 374 | } 375 | } 376 | 377 | if(cmdParser.HasKey(L"c")) 378 | { 379 | settings.bCopyFiles = true; 380 | if(cmdParser.HasKey(L"f")) 381 | settings.bForceCopy = true; 382 | else 383 | if(cmdParser.HasKey(L"v")) 384 | settings.bCopyIfNewerOrHigherVer = true; 385 | } 386 | 387 | if(cmdParser.HasKey(L"d")) 388 | settings.bDontWaitForTerminate = true; 389 | 390 | if(cmdParser.HasKey(L"i")) 391 | { 392 | settings.bInteractive = true; 393 | settings.sessionToInteractWith = (DWORD)-1; //default, meaning interactive session 394 | if(cmdParser.HasVal(L"i")) 395 | settings.sessionToInteractWith = wtodw(cmdParser.GetVal(L"i")); 396 | } 397 | 398 | if(cmdParser.HasKey(L"h")) 399 | { 400 | if(settings.bUseSystemAccount || settings.bRunLimited) 401 | { 402 | Log(L"Can't use -h and -l together", true); 403 | return false; 404 | } 405 | if(!settings.bUseSystemAccount) //ignore if -s already given 406 | settings.bRunElevated = true; 407 | } 408 | 409 | if(cmdParser.HasKey(L"l")) 410 | { 411 | if(settings.bUseSystemAccount || settings.bRunElevated) 412 | { 413 | Log(L"Can't use -h, -s, or -l together", true); 414 | return false; 415 | } 416 | settings.bRunLimited = true; 417 | } 418 | 419 | if(cmdParser.HasKey(L"n")) 420 | { 421 | if(settings.computerList.empty()) 422 | { 423 | Log(L"Setting -n when no computers specified", true); 424 | return false; 425 | } 426 | settings.remoteCompConnectTimeoutSec = wtodw(cmdParser.GetVal(L"n")); 427 | if(0 == settings.remoteCompConnectTimeoutSec) 428 | { 429 | Log(L"Setting -n to 0", true); 430 | return false; 431 | } 432 | } 433 | 434 | bool bPasswordSet = false; 435 | if(cmdParser.HasKey(L"p")) 436 | { 437 | settings.password = cmdParser.GetVal(L"p"); 438 | bPasswordSet = true; 439 | //empty passwords are supported 440 | } 441 | 442 | if(cmdParser.HasKey(L"p@")) 443 | { 444 | CString content, passFile = cmdParser.GetVal(L"p@"); 445 | passFile.Trim(L"\""); //remove possible quotes 446 | ReadTextFile(passFile, content); 447 | wchar_t* pC = wcstok(content.LockBuffer(), L"\r\n"); 448 | settings.password = pC; 449 | settings.password.Trim(L" "); 450 | content.UnlockBuffer(); 451 | bPasswordSet = true; 452 | if(cmdParser.HasKey(L"p@d")) 453 | if(FALSE == DeleteFile(passFile)) 454 | Log(L"Failed to delete p@d file", false); 455 | } 456 | 457 | if(cmdParser.HasKey(L"u")) 458 | { 459 | settings.user = cmdParser.GetVal(L"u"); 460 | if(settings.user.IsEmpty()) 461 | { 462 | Log(L"-u without user", true); 463 | return false; 464 | } 465 | 466 | if(false == bPasswordSet) 467 | { 468 | #define PW_BUFF_LEN 500 469 | wchar_t pwBuffer[PW_BUFF_LEN] = {0}; 470 | wprintf(L"Password: "); 471 | 472 | // Set the console mode to no-echo, not-line-buffered input 473 | DWORD mode; 474 | HANDLE ih = GetStdHandle( STD_INPUT_HANDLE ); 475 | GetConsoleMode(ih, &mode); 476 | SetConsoleMode(ih, (mode & ~ENABLE_ECHO_INPUT) | ENABLE_LINE_INPUT); 477 | DWORD read = 0; 478 | ReadConsole(ih, pwBuffer, PW_BUFF_LEN - 1, &read, NULL); 479 | pwBuffer[read] = L'\0'; 480 | settings.password = pwBuffer; 481 | settings.password.TrimRight(L"\r\n"); 482 | SetConsoleMode(ih, mode); // Restore the console mode 483 | wprintf(L"\r\n"); 484 | if(gbStop) 485 | return false; 486 | } 487 | } 488 | 489 | if(cmdParser.HasKey(L"s")) 490 | { 491 | //It IS OK to use -u and -s (-u connects to server, then app is launched as -s) 492 | //if(FALSE == settings.user.IsEmpty()) 493 | //{ 494 | // Log(L"Specified -s and -u"); 495 | // return false; 496 | //} 497 | if(settings.bRunLimited) 498 | { 499 | Log(L"Can't use -s and -l together", true); 500 | return false; 501 | } 502 | if(settings.bRunElevated) 503 | settings.bRunElevated = false; //ignore -h if -s given 504 | settings.bUseSystemAccount = true; 505 | } 506 | else 507 | if(cmdParser.HasKey(L"e")) 508 | settings.bDontLoadProfile = true; 509 | 510 | if(cmdParser.HasKey(L"w")) 511 | { 512 | settings.workingDir = cmdParser.GetVal(L"w"); 513 | if(settings.workingDir.IsEmpty()) 514 | { 515 | Log(L"-w without value", true); 516 | return false; 517 | } 518 | } 519 | 520 | if(cmdParser.HasKey(L"x")) 521 | { 522 | if(false == settings.bUseSystemAccount) 523 | { 524 | Log(L"Specified -x without -s", true); 525 | return false; 526 | } 527 | settings.bShowUIOnWinLogon = true; 528 | } 529 | 530 | //priority 531 | if(cmdParser.HasKey(L"low")) 532 | settings.priority = BELOW_NORMAL_PRIORITY_CLASS; 533 | if(cmdParser.HasKey(L"belownormal")) 534 | settings.priority = BELOW_NORMAL_PRIORITY_CLASS; 535 | if(cmdParser.HasKey(L"abovenormal")) 536 | settings.priority = ABOVE_NORMAL_PRIORITY_CLASS; 537 | if(cmdParser.HasKey(L"high")) 538 | settings.priority = HIGH_PRIORITY_CLASS; 539 | if(cmdParser.HasKey(L"realtime")) 540 | settings.priority = REALTIME_PRIORITY_CLASS; 541 | if(cmdParser.HasKey(L"background")) 542 | settings.priority = IDLE_PRIORITY_CLASS; 543 | 544 | if(cmdParser.HasKey(L"dfr")) 545 | settings.bDisableFileRedirection = true; 546 | 547 | if(cmdParser.HasKey(L"noname")) 548 | settings.bNoName = true; 549 | else if (cmdParser.HasKey(L"sname")) 550 | settings.serviceName = cmdParser.GetVal(L"sname"); 551 | 552 | if (cmdParser.HasKey(L"share")) { 553 | settings.targetShare = cmdParser.GetVal(L"share"); 554 | if (cmdParser.HasKey(L"sharepath")) { 555 | settings.targetSharePath = cmdParser.GetVal(L"sharepath"); 556 | } 557 | } 558 | 559 | 560 | if(cmdParser.HasKey(L"csrc")) 561 | { 562 | if(false == settings.bCopyFiles) 563 | { 564 | Log(L"-csrc without -c", true); 565 | return false; 566 | } 567 | CString tmp = cmdParser.GetVal(L"csrc"); 568 | if(tmp.IsEmpty()) 569 | { 570 | Log(L"-csrc without value", true); 571 | return false; 572 | } 573 | 574 | //split tmp into directory and file 575 | LPWSTR cPtr = (LPWSTR)wcsrchr(tmp.LockBuffer(), L'\\'); 576 | if(NULL != cPtr) 577 | { 578 | *cPtr = L'\0'; //truncate at filename 579 | cPtr++; 580 | FileInfo fi; 581 | fi.filenameOnly = cPtr; 582 | settings.srcFileInfos.push_back(fi); 583 | } 584 | tmp.UnlockBuffer(); 585 | settings.srcDir = (LPCWSTR)tmp; //store folder 586 | } 587 | 588 | if(cmdParser.HasKey(L"clist")) 589 | { 590 | if(false == settings.srcFileInfos.empty()) 591 | { 592 | Log(L"-csrc and -clist are not compatible", true); 593 | return false; 594 | } 595 | 596 | if(false == settings.bCopyFiles) 597 | { 598 | Log(L"-clist without -c", true); 599 | return false; 600 | } 601 | 602 | CString tmp = cmdParser.GetVal(L"clist"); 603 | if(tmp.IsEmpty()) 604 | { 605 | Log(L"-clist without value", true); 606 | return false; 607 | } 608 | tmp = ExpandToFullPath(tmp); //search on path if no path specified 609 | 610 | CString content; 611 | if(false == ReadTextFile(tmp, content)) 612 | return false; 613 | 614 | //split tmp into directory and file 615 | LPWSTR cPtr = (LPWSTR)wcsrchr(tmp.LockBuffer(), L'\\'); 616 | if(NULL != cPtr) 617 | *cPtr = L'\0'; //truncate at filename 618 | tmp.UnlockBuffer(); 619 | settings.srcDir = (LPCWSTR)tmp; //store folder 620 | 621 | wchar_t* pC = wcstok(content.LockBuffer(), L"\r\n"); 622 | while(NULL != pC) 623 | { 624 | CString s = pC; 625 | s.Trim(); 626 | if(FALSE == s.IsEmpty()) 627 | { 628 | FileInfo fi; 629 | fi.filenameOnly = s; 630 | settings.srcFileInfos.push_back(fi); 631 | } 632 | pC = wcstok(NULL, L"\r\n"); 633 | } 634 | if(settings.srcFileInfos.empty()) 635 | { 636 | Log(L"-clist file empty", true); 637 | return false; 638 | } 639 | } 640 | 641 | if(cmdParser.HasKey(L"cnodel")) 642 | { 643 | if(false == settings.bCopyFiles) 644 | { 645 | Log(L"-cnodel without -c", true); 646 | return false; 647 | } 648 | settings.bNoDelete = true; 649 | } 650 | 651 | if(cmdParser.HasKey(L"rlo")) 652 | { 653 | settings.remoteLogPath = cmdParser.GetVal(L"rlo"); 654 | if(settings.remoteLogPath.IsEmpty()) 655 | { 656 | Log(L"-rlo missing value", true); 657 | return false; 658 | } 659 | } 660 | 661 | if(cmdParser.HasKey(L"to")) 662 | { 663 | if(settings.bDontWaitForTerminate) 664 | { 665 | Log(L"-d and -to cannot be used together", true); 666 | return false; 667 | } 668 | settings.timeoutSeconds = wtodw(cmdParser.GetVal(L"to")); 669 | if(settings.timeoutSeconds == 0) 670 | { 671 | Log(L"invalid or missing value for -to", true); 672 | return false; 673 | } 674 | } 675 | 676 | //if(settings.bUseSystemAccount && settings.bRunLimited) 677 | //{ 678 | // Log(L"Can't run as System account, and run limited"); 679 | // return false; 680 | //} 681 | 682 | //if((FALSE == settings.user.IsEmpty()) && settings.bRunLimited) 683 | //{ 684 | // Log(L"Can't run limited and specify an account"); 685 | // return false; 686 | //} 687 | 688 | //program 689 | LPCWSTR pArgPtr = pAppStart; 690 | if(L'"' == *pAppStart) 691 | { 692 | pAppStart++; 693 | pArgPtr = wcschr(pAppStart, L'"'); 694 | if(NULL == pArgPtr) 695 | return false; 696 | settings.app = CString(pAppStart).Left(pArgPtr - pAppStart); 697 | pArgPtr++; //get past quote we're on 698 | } 699 | else 700 | { 701 | //not quoted, so must be white space delimited 702 | while(!iswspace(*pArgPtr) && *pArgPtr) 703 | pArgPtr++; 704 | settings.app = CString(pAppStart).Left(pArgPtr - pAppStart); 705 | } 706 | 707 | if(settings.app.IsEmpty()) 708 | { 709 | Log(L"No application specified", true); 710 | return false; 711 | } 712 | 713 | //arguments 714 | pArgPtr = EatWhiteSpace(pArgPtr); 715 | 716 | settings.appArgs = pArgPtr; 717 | 718 | if(settings.bCopyFiles) //if not copying files, it's OK if we can't find some 719 | { 720 | //now create destFileList based on app and srcFileList 721 | FileInfo fi; 722 | fi.filenameOnly = settings.app; 723 | settings.destFileInfos.push_back(fi); //index 0 724 | 725 | if(settings.srcFileInfos.empty()) 726 | { 727 | //no source file specified (no -csrc or -clist) so just use settings.app, and resolve the path from it 728 | settings.srcFileInfos.push_back(fi); 729 | } 730 | 731 | _ASSERT(false == settings.srcFileInfos.empty()); 732 | if(false == settings.srcFileInfos.empty()) 733 | { 734 | std::vector::iterator itr = settings.srcFileInfos.begin(); 735 | itr++; //skip over app (actual target executable) which we've already added to the list 736 | 737 | //copy rest of files (from -clist) if any 738 | while(itr != settings.srcFileInfos.end()) 739 | { 740 | settings.destFileInfos.push_back(*itr); 741 | itr++; 742 | } 743 | } 744 | 745 | //read in source file information to verify all required source files exist 746 | if(false == settings.ResolveFilePaths()) 747 | { 748 | Log(L"One or more source files were not found. Try -csrc or -clist?", true); 749 | return false; 750 | } 751 | } 752 | 753 | return true; 754 | } 755 | 756 | return false; 757 | } 758 | -------------------------------------------------------------------------------- /Process.cpp: -------------------------------------------------------------------------------- 1 | // Process.cpp: Executing requested process 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | 11 | #include "stdafx.h" 12 | #include "PAExec.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | #ifdef _DEBUG 21 | #define new DEBUG_NEW 22 | #undef THIS_FILE 23 | static char THIS_FILE[] = __FILE__; 24 | #endif 25 | 26 | 27 | HANDLE GetLocalSystemProcessToken(); 28 | bool LimitRights(HANDLE& hUser); 29 | bool ElevateUserToken(HANDLE& hEnvUser); 30 | 31 | void DuplicateTokenToIncreaseRights(HANDLE& h, LPCSTR file, int line) 32 | { 33 | HANDLE hDupe = NULL; 34 | if(DuplicateTokenEx(h, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hDupe)) 35 | { 36 | CloseHandle(h); 37 | h = hDupe; 38 | hDupe = NULL; 39 | } 40 | else 41 | { 42 | DWORD gle = GetLastError(); 43 | _ASSERT(0); 44 | Log(StrFormat(L"Error duplicating a user token (%S, %d)", file, line), GetLastError()); 45 | } 46 | } 47 | 48 | #ifdef _DEBUG 49 | void GrantAllPrivs(HANDLE h) 50 | { 51 | Log(L"DEBUG: GrantAllPrivs", false); 52 | CString privs = L"SeCreateTokenPrivilege,SeAssignPrimaryTokenPrivilege,SeLockMemoryPrivilege,SeIncreaseQuotaPrivilege,SeMachineAccountPrivilege," 53 | L"SeTcbPrivilege,SeSecurityPrivilege,SeTakeOwnershipPrivilege,SeLoadDriverPrivilege,SeSystemProfilePrivilege,SeSystemtimePrivilege,SeProfileSingleProcessPrivilege," 54 | L"SeIncreaseBasePriorityPrivilege,SeCreatePagefilePrivilege,SeCreatePermanentPrivilege,SeBackupPrivilege,SeRestorePrivilege,SeShutdownPrivilege,SeDebugPrivilege," 55 | L"SeAuditPrivilege,SeSystemEnvironmentPrivilege,SeChangeNotifyPrivilege,SeRemoteShutdownPrivilege,SeUndockPrivilege,SeSyncAgentPrivilege,SeEnableDelegationPrivilege," 56 | L"SeManageVolumePrivilege,SeImpersonatePrivilege,SeCreateGlobalPrivilege,SeTrustedCredManAccessPrivilege,SeRelabelPrivilege,SeIncreaseWorkingSetPrivilege," 57 | L"SeTimeZonePrivilege,SeCreateSymbolicLinkPrivilege"; 58 | 59 | wchar_t* pC = wcstok(privs.LockBuffer(), L","); 60 | while(NULL != pC) 61 | { 62 | EnablePrivilege(pC, h); //needed to call CreateProcessAsUser 63 | pC = wcstok(NULL, L","); 64 | } 65 | } 66 | #endif 67 | 68 | 69 | void GetUserDomain(LPCWSTR userIn, CString& user, CString& domain) 70 | { 71 | //run as specified user 72 | CString tmp = userIn; 73 | LPCWSTR userStr = NULL, domainStr = NULL; 74 | if(NULL != wcschr(userIn, L'@')) 75 | userStr = userIn; //leave domain as NULL 76 | else 77 | { 78 | if(NULL != wcschr(userIn, L'\\')) 79 | { 80 | domainStr = wcstok(tmp.LockBuffer(), L"\\"); 81 | userStr = wcstok(NULL, L"\\"); 82 | } 83 | else 84 | { 85 | //no domain given 86 | userStr = userIn; 87 | domainStr = L"."; 88 | } 89 | } 90 | user = userStr; 91 | domain = domainStr; 92 | } 93 | 94 | 95 | bool GetUserHandle(Settings& settings, BOOL& bLoadedProfile, PROFILEINFO& profile, HANDLE hCmdPipe) 96 | { 97 | DWORD gle = 0; 98 | 99 | if(settings.bUseSystemAccount) 100 | { 101 | if(BAD_HANDLE(settings.hUser)) //might already have hUser from a previous call 102 | { 103 | EnablePrivilege(SE_DEBUG_NAME); //helps with OpenProcess, required for GetLocalSystemProcessToken 104 | settings.hUser = GetLocalSystemProcessToken(); 105 | if(BAD_HANDLE(settings.hUser)) 106 | { 107 | Log(L"Not able to get Local System token", true); 108 | return false; 109 | } 110 | else 111 | Log(L"Got Local System handle", false); 112 | 113 | DuplicateTokenToIncreaseRights(settings.hUser, __FILE__, __LINE__); 114 | } 115 | return true; 116 | } 117 | else 118 | { 119 | //not Local System, so either as specified user, or as current user 120 | if(FALSE == settings.user.IsEmpty()) 121 | { 122 | CString user, domain; 123 | GetUserDomain(settings.user, user, domain); 124 | 125 | BOOL bLoggedIn = LogonUser(user, domain.IsEmpty() ? NULL : domain, settings.password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_WINNT50, &settings.hUser); 126 | gle = GetLastError(); 127 | #ifdef _DEBUG 128 | Log(L"DEBUG: LogonUser", gle); 129 | #endif 130 | if((FALSE == bLoggedIn) || BAD_HANDLE(settings.hUser)) 131 | { 132 | Log(StrFormat(L"Error logging in as %s.", settings.user), gle); 133 | return false; 134 | } 135 | else 136 | DuplicateTokenToIncreaseRights(settings.hUser, __FILE__, __LINE__); //gives max rights 137 | 138 | if(!BAD_HANDLE(settings.hUser) && (false == settings.bDontLoadProfile)) 139 | { 140 | EnablePrivilege(SE_RESTORE_NAME); 141 | EnablePrivilege(SE_BACKUP_NAME); 142 | bLoadedProfile = LoadUserProfile(settings.hUser, &profile); 143 | if(FALSE == bLoadedProfile) 144 | Log(L"Failed to load user profile", GetLastError()); 145 | } 146 | return true; 147 | } 148 | else 149 | { 150 | //run as current user 151 | 152 | if(NULL != hCmdPipe) 153 | { 154 | BOOL b = ImpersonateNamedPipeClient(hCmdPipe); 155 | DWORD gle = GetLastError(); 156 | if(FALSE == b) 157 | Log(L"Failed to impersonate client user", gle); 158 | else 159 | Log(L"Impersonated caller", false); 160 | } 161 | 162 | HANDLE hThread = GetCurrentThread(); 163 | BOOL bDupe = DuplicateHandle(GetCurrentProcess(), hThread, GetCurrentProcess(), &hThread, 0, TRUE, DUPLICATE_SAME_ACCESS); 164 | DWORD gle = GetLastError(); 165 | 166 | BOOL bOpen = OpenThreadToken(hThread, TOKEN_DUPLICATE | TOKEN_QUERY, TRUE, &settings.hUser); 167 | gle = GetLastError(); 168 | if(1008 == gle) //no thread token 169 | { 170 | bOpen = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &settings.hUser); 171 | gle = GetLastError(); 172 | } 173 | 174 | if(FALSE == bOpen) 175 | Log(L"Failed to open current user token", GetLastError()); 176 | 177 | DuplicateTokenToIncreaseRights(settings.hUser, __FILE__, __LINE__); //gives max rights 178 | RevertToSelf(); 179 | return !BAD_HANDLE(settings.hUser); 180 | } 181 | } 182 | } 183 | 184 | bool StartProcess(Settings& settings, HANDLE hCmdPipe) 185 | { 186 | //Launching as one of: 187 | //1. System Account 188 | //2. Specified account (or limited account) 189 | //3. As current process 190 | 191 | DWORD gle = 0; 192 | 193 | BOOL bLoadedProfile = FALSE; 194 | PROFILEINFO profile = {0}; 195 | profile.dwSize = sizeof(profile); 196 | profile.lpUserName = (LPWSTR)(LPCWSTR)settings.user; 197 | profile.dwFlags = PI_NOUI; 198 | 199 | if(false == GetUserHandle(settings, bLoadedProfile, profile, hCmdPipe)) 200 | return false; 201 | 202 | PROCESS_INFORMATION pi = {0}; 203 | STARTUPINFO si = {0}; 204 | si.cb = sizeof(si); 205 | si.dwFlags = STARTF_USESHOWWINDOW; 206 | si.wShowWindow = SW_SHOW; 207 | if(!(settings.bInteractive || settings.bShowUIOnWinLogon)) 208 | si.wShowWindow = SW_HIDE; 209 | if(!BAD_HANDLE(settings.hStdErr)) 210 | { 211 | si.hStdError = settings.hStdErr; 212 | si.hStdInput = settings.hStdIn; 213 | si.hStdOutput = settings.hStdOut; 214 | si.dwFlags |= STARTF_USESTDHANDLES; 215 | #ifdef _DEBUG 216 | Log(L"DEBUG: Using redirected handles", false); 217 | #endif 218 | } 219 | #ifdef _DEBUG 220 | else 221 | Log(L"DEBUG: Not using redirected IO", false); 222 | #endif 223 | 224 | CString path = StrFormat(L"\"%s\"", settings.app); 225 | if(FALSE == settings.appArgs.IsEmpty()) 226 | { 227 | path += L" "; 228 | path += settings.appArgs; 229 | } 230 | 231 | LPCWSTR startingDir = NULL; 232 | if(FALSE == settings.workingDir.IsEmpty()) 233 | startingDir = settings.workingDir; 234 | 235 | DWORD launchGLE = 0; 236 | 237 | //do the next two calls BEFORE PrepForInteractiveProcess so the session ID stays set 238 | if (settings.bRunLimited) 239 | if (false == LimitRights(settings.hUser)) 240 | return false; 241 | 242 | if (settings.bRunElevated) 243 | if (false == ElevateUserToken(settings.hUser)) 244 | return false; 245 | 246 | CleanupInteractive ci = {0}; 247 | 248 | if(settings.bInteractive || settings.bShowUIOnWinLogon) 249 | { 250 | BOOL b = PrepForInteractiveProcess(settings, &ci); 251 | if(FALSE == b) 252 | Log(L"Failed to PrepForInteractiveProcess", true); 253 | 254 | if(NULL == si.lpDesktop) 255 | si.lpDesktop = L"Winsta0\\default"; 256 | if(settings.bShowUIOnWinLogon) 257 | si.lpDesktop = L"WinSta0\\Winlogon"; 258 | //Log(StrFormat(L"Using desktop: %s", si.lpDesktop), false); 259 | //http://blogs.msdn.com/b/winsdk/archive/2009/07/14/launching-an-interactive-process-from-windows-service-in-windows-vista-and-later.aspx 260 | //indicates desktop names are case sensitive 261 | } 262 | #ifdef _DEBUG 263 | Log(StrFormat(L"DEBUG: PAExec using desktop %s", si.lpDesktop == NULL ? L"{default}" : si.lpDesktop), false); 264 | #endif 265 | 266 | DWORD dwFlags = CREATE_SUSPENDED | CREATE_NEW_CONSOLE; 267 | 268 | LPVOID pEnvironment = NULL; 269 | VERIFY(CreateEnvironmentBlock(&pEnvironment, settings.hUser, TRUE)); 270 | if(NULL != pEnvironment) 271 | dwFlags |= CREATE_UNICODE_ENVIRONMENT; 272 | #ifdef _DEBUG 273 | gle = GetLastError(); 274 | Log(L"DEBUG: CreateEnvironmentBlock", gle); 275 | #endif 276 | 277 | if(settings.bDisableFileRedirection) 278 | DisableFileRedirection(); 279 | 280 | CString user, domain; 281 | GetUserDomain(settings.user, user, domain); 282 | 283 | #ifdef _DEBUG 284 | Log(StrFormat(L"DEBUG: U:%s D:%s P:%s bP:%d Env:%s WD:%s", 285 | user, domain, settings.password, settings.bDontLoadProfile, 286 | pEnvironment ? L"true" : L"null", startingDir ? startingDir : L"null"), false); 287 | #endif 288 | 289 | BOOL bLaunched = FALSE; 290 | 291 | DWORD usingSessionID = (DWORD)-1; 292 | DWORD ignored = 0; 293 | if (FALSE == GetTokenInformation(settings.hUser, TokenSessionId, &usingSessionID, sizeof(usingSessionID), &ignored)) 294 | Log(L"GetTokenInformation[2] failed", GetLastError()); 295 | else 296 | Log(StrFormat(L"User token sessionID: %d", usingSessionID), (DWORD)0); 297 | 298 | if(settings.bUseSystemAccount) 299 | { 300 | Log(StrFormat(L"PAExec starting process [%s] as Local System", path), false); 301 | 302 | if(BAD_HANDLE(settings.hUser)) 303 | Log(L"Have bad user handle", true); 304 | 305 | EnablePrivilege(SE_IMPERSONATE_NAME); 306 | BOOL bImpersonated = ImpersonateLoggedOnUser(settings.hUser); 307 | if(FALSE == bImpersonated) 308 | { 309 | Log(L"Failed to impersonate", GetLastError()); 310 | _ASSERT(bImpersonated); 311 | } 312 | EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); 313 | EnablePrivilege(SE_INCREASE_QUOTA_NAME); 314 | bLaunched = CreateProcessAsUser(settings.hUser, NULL, path.LockBuffer(), NULL, NULL, TRUE, dwFlags, pEnvironment, startingDir, &si, &pi); 315 | launchGLE = GetLastError(); 316 | Log(StrFormat(L"CreateProcessAsUser [sys], PID:%u", pi.dwProcessId), launchGLE); 317 | path.UnlockBuffer(); 318 | 319 | #ifdef _DEBUG 320 | if(0 != launchGLE) 321 | Log(StrFormat(L"Launch (launchGLE=%u) params: user=[x%X] path=[%s] flags=[x%X], pEnv=[%s], dir=[%s], stdin=[x%X], stdout=[x%X], stderr=[x%X]", 322 | launchGLE, (DWORD)settings.hUser, path, dwFlags, pEnvironment ? L"{env}" : L"{null}", startingDir ? startingDir : L"{null}", 323 | (DWORD)si.hStdInput, (DWORD)si.hStdOutput, (DWORD)si.hStdError), false); 324 | #endif 325 | 326 | RevertToSelf(); 327 | } 328 | else 329 | { 330 | if(FALSE == settings.user.IsEmpty()) //launching as a specific user 331 | { 332 | Log(StrFormat(L"PAExec starting process [%s] as %s", path, settings.user), false); 333 | 334 | if(false == settings.bRunLimited) 335 | { 336 | if(!BAD_HANDLE(settings.hUser)) 337 | { 338 | bLaunched = CreateProcessAsUser(settings.hUser, NULL, path.LockBuffer(), NULL, NULL, TRUE, dwFlags, pEnvironment, startingDir, &si, &pi); 339 | launchGLE = GetLastError(); 340 | Log(StrFormat(L"CreateProcessAsUser, PID:%u", pi.dwProcessId), launchGLE); 341 | } 342 | else //fall back to old code 343 | { 344 | bLaunched = CreateProcessWithLogonW(user, domain.IsEmpty() ? NULL : domain, settings.password, settings.bDontLoadProfile ? 0 : LOGON_WITH_PROFILE, NULL, path.LockBuffer(), dwFlags, pEnvironment, startingDir, &si, &pi); 345 | launchGLE = GetLastError(); 346 | Log(StrFormat(L"CreateProcessWithLogonW, PID:%u", pi.dwProcessId), launchGLE); 347 | } 348 | 349 | path.UnlockBuffer(); 350 | 351 | #ifdef _DEBUG 352 | if(0 != launchGLE) 353 | Log(StrFormat(L"Launch (launchGLE=%u) params: user=[%s] domain=[%s] prof=[x%X] path=[%s] flags=[x%X], pEnv=[%s], dir=[%s], stdin=[x%X], stdout=[x%X], stderr=[x%X]", 354 | launchGLE, user, domain, settings.bDontLoadProfile ? 0 : LOGON_WITH_PROFILE, 355 | path, dwFlags, pEnvironment ? L"{env}" : L"{null}", startingDir ? startingDir : L"{null}", 356 | (DWORD)si.hStdInput, (DWORD)si.hStdOutput, (DWORD)si.hStdError), false); 357 | #endif 358 | } 359 | else 360 | bLaunched = FALSE; //force to run with CreateProcessAsUser so rights can be limited 361 | 362 | //CreateProcessWithLogonW can't be called from LocalSystem on Win2003 and earlier, so LogonUser/CreateProcessAsUser must be used. Might as well try for everyone 363 | if((FALSE == bLaunched) && !BAD_HANDLE(settings.hUser)) 364 | { 365 | #ifdef _DEBUG 366 | Log(L"DEBUG: Failed CreateProcessWithLogonW - trying CreateProcessAsUser", GetLastError()); 367 | #endif 368 | EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); 369 | EnablePrivilege(SE_INCREASE_QUOTA_NAME); 370 | EnablePrivilege(SE_IMPERSONATE_NAME); 371 | BOOL bImpersonated = ImpersonateLoggedOnUser(settings.hUser); 372 | if(FALSE == bImpersonated) 373 | { 374 | Log(L"Failed to impersonate", GetLastError()); 375 | _ASSERT(bImpersonated); 376 | } 377 | 378 | bLaunched = CreateProcessAsUser(settings.hUser, NULL, path.LockBuffer(), NULL, NULL, TRUE, CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, pEnvironment, startingDir, &si, &pi); 379 | if(0 == GetLastError()) 380 | launchGLE = 0; //mark as successful, otherwise return our original error 381 | else 382 | { 383 | DWORD tmp = GetLastError(); 384 | Log(StrFormat(L"CreateProcessAsUser[2], PID:%u", pi.dwProcessId), tmp); 385 | } 386 | 387 | path.UnlockBuffer(); 388 | #ifdef _DEBUG 389 | if(0 != launchGLE) 390 | Log(StrFormat(L"Launch (launchGLE=%u) params: user=[x%X] path=[%s] pEnv=[%s], dir=[%s], stdin=[x%X], stdout=[x%X], stderr=[x%X]", 391 | launchGLE, (DWORD)settings.hUser, path, pEnvironment ? L"{env}" : L"{null}", startingDir ? startingDir : L"{null}", 392 | (DWORD)si.hStdInput, (DWORD)si.hStdOutput, (DWORD)si.hStdError), false); 393 | #endif 394 | RevertToSelf(); 395 | } 396 | } 397 | else 398 | { 399 | Log(StrFormat(L"PAExec starting process [%s] as current user", path), false); 400 | 401 | EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); 402 | EnablePrivilege(SE_INCREASE_QUOTA_NAME); 403 | EnablePrivilege(SE_IMPERSONATE_NAME); 404 | 405 | if(NULL != settings.hUser) 406 | { 407 | bLaunched = CreateProcessAsUser(settings.hUser, NULL, path.LockBuffer(), NULL, NULL, TRUE, dwFlags, pEnvironment, startingDir, &si, &pi); 408 | launchGLE = GetLastError(); 409 | Log(StrFormat(L"CreateProcessAsUser[3], PID:%u", pi.dwProcessId), launchGLE); 410 | } 411 | if(FALSE == bLaunched) 412 | { 413 | bLaunched = CreateProcess(NULL, path.LockBuffer(), NULL, NULL, TRUE, dwFlags, pEnvironment, startingDir, &si, &pi); 414 | launchGLE = GetLastError(); 415 | Log(StrFormat(L"CreateProcess, PID:%u", pi.dwProcessId), launchGLE); 416 | } 417 | 418 | //#ifdef _DEBUG 419 | if(0 != launchGLE) 420 | Log(StrFormat(L"Launch (launchGLE=%u) params: path=[%s] user=[%s], pEnv=[%s], dir=[%s], stdin=[x%X], stdout=[x%X], stderr=[x%X]", 421 | launchGLE, path, settings.hUser ? L"{non-null}" : L"{null}", pEnvironment ? L"{env}" : L"{null}", startingDir ? startingDir : L"{null}", 422 | (DWORD)si.hStdInput, (DWORD)si.hStdOutput, (DWORD)si.hStdError), false); 423 | //#endif 424 | path.UnlockBuffer(); 425 | } 426 | } 427 | 428 | if(bLaunched) 429 | { 430 | if(gbInService) 431 | Log(L"Successfully launched", false); 432 | 433 | settings.hProcess = pi.hProcess; 434 | settings.processID = pi.dwProcessId; 435 | 436 | if(false == settings.allowedProcessors.empty()) 437 | { 438 | DWORD sysMask = 0, procMask = 0; 439 | VERIFY(GetProcessAffinityMask(pi.hProcess, &procMask, &sysMask)); 440 | procMask = 0; 441 | for(std::vector::iterator itr = settings.allowedProcessors.begin(); settings.allowedProcessors.end() != itr; itr++) 442 | { 443 | DWORD bit = 1; 444 | bit = bit << (*itr - 1); 445 | procMask |= bit & sysMask; 446 | } 447 | VERIFY(SetProcessAffinityMask(pi.hProcess, procMask)); 448 | } 449 | 450 | VERIFY(SetPriorityClass(pi.hProcess, settings.priority)); 451 | ResumeThread(pi.hThread); 452 | VERIFY(CloseHandle(pi.hThread)); 453 | } 454 | else 455 | { 456 | Log(StrFormat(L"Failed to start %s.", path), launchGLE); 457 | if((ERROR_ELEVATION_REQUIRED == launchGLE) && (false == gbInService)) 458 | Log(L"HINT: PAExec probably needs to be \"Run As Administrator\"", false); 459 | } 460 | 461 | if(ci.bPreped) 462 | CleanUpInteractiveProcess(&ci); 463 | 464 | if(settings.bDisableFileRedirection) 465 | RevertFileRedirection(); 466 | 467 | if(NULL != pEnvironment) 468 | DestroyEnvironmentBlock(pEnvironment); 469 | pEnvironment = NULL; 470 | 471 | if(bLoadedProfile) 472 | UnloadUserProfile(settings.hUser, profile.hProfile); 473 | 474 | if(!BAD_HANDLE(settings.hUser)) 475 | { 476 | CloseHandle(settings.hUser); 477 | settings.hUser = NULL; 478 | } 479 | 480 | return bLaunched ? true : false; 481 | } 482 | 483 | 484 | 485 | CString GetTokenUserSID(HANDLE hToken) 486 | { 487 | DWORD tmp = 0; 488 | CString userName; 489 | DWORD sidNameSize = 64; 490 | std::vector sidName; 491 | sidName.resize(sidNameSize); 492 | 493 | DWORD sidDomainSize = 64; 494 | std::vector sidDomain; 495 | sidDomain.resize(sidNameSize); 496 | 497 | DWORD userTokenSize = 1024; 498 | std::vector tokenUserBuf; 499 | tokenUserBuf.resize(userTokenSize); 500 | 501 | TOKEN_USER *userToken = (TOKEN_USER*)&tokenUserBuf.front(); 502 | 503 | if(GetTokenInformation(hToken, TokenUser, userToken, userTokenSize, &tmp)) 504 | { 505 | WCHAR *pSidString = NULL; 506 | if(ConvertSidToStringSid(userToken->User.Sid, &pSidString)) 507 | userName = pSidString; 508 | if(NULL != pSidString) 509 | LocalFree(pSidString); 510 | } 511 | else 512 | _ASSERT(0); 513 | 514 | return userName; 515 | } 516 | 517 | HANDLE GetLocalSystemProcessToken() 518 | { 519 | DWORD pids[1024*10] = {0}, cbNeeded = 0, cProcesses = 0; 520 | 521 | if ( !EnumProcesses(pids, sizeof(pids), &cbNeeded)) 522 | { 523 | Log(L"Can't enumProcesses - Failed to get token for Local System.", true); 524 | return NULL; 525 | } 526 | 527 | // Calculate how many process identifiers were returned. 528 | cProcesses = cbNeeded / sizeof(DWORD); 529 | for(DWORD i = 0; i 13 | 14 | #ifdef _DEBUG 15 | #define new DEBUG_NEW 16 | #undef THIS_FILE 17 | static char THIS_FILE[] = __FILE__; 18 | #endif 19 | 20 | 21 | __time64_t gLastClientContact = 0; 22 | bool gbStop = false; 23 | SERVICE_STATUS_HANDLE ghService = NULL; 24 | SERVICE_STATUS gServiceStatus = {0}; 25 | static bool gbDevOnlyDebug = false; 26 | bool gbInService = false; 27 | extern volatile long gInProcessRequests; 28 | 29 | void __cdecl StopServiceAsync(void*) 30 | { 31 | while(false == gbStop) 32 | { 33 | Sleep(5000); 34 | if(0 == gInProcessRequests) 35 | { 36 | //wait 2 more seconds to see if it's still at zero to hope we didn't just catch it in between requests 37 | Sleep(2000); 38 | if(0 == gInProcessRequests) 39 | { 40 | Log(L"PAExec service stopping (async)", false); 41 | gbStop = true; //signals to other places in app to stop too 42 | if(NULL != ghService) 43 | { 44 | gServiceStatus.dwCurrentState = SERVICE_STOPPED; 45 | SetServiceStatus(ghService, &gServiceStatus); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | void WINAPI ServiceControlHandler(DWORD dwControl) 53 | { 54 | switch(dwControl) 55 | { 56 | case SERVICE_CONTROL_SHUTDOWN: 57 | case SERVICE_CONTROL_STOP: 58 | if(0 == gInProcessRequests) 59 | { 60 | gbStop = true; 61 | _ASSERT(NULL != ghService); 62 | if(NULL != ghService) 63 | { 64 | Log(L"PAExec service stopping", false); 65 | gServiceStatus.dwCurrentState = SERVICE_STOPPED; 66 | SetServiceStatus(ghService, &gServiceStatus); 67 | } 68 | } 69 | else 70 | { 71 | Log(L"PAExec received request to stop, but still have active requests. Will stop later.", false); 72 | _beginthread(StopServiceAsync, 0, 0); 73 | } 74 | break; 75 | } 76 | } 77 | 78 | #define BUFF_SIZE_HINT 16384 79 | 80 | UINT WINAPI PipeClientThreadProc(void* p) 81 | { 82 | HANDLE hPipe = (HANDLE)p; 83 | 84 | bool bBail = false; 85 | 86 | while(true) 87 | { 88 | RemMsg request, response; 89 | 90 | BYTE buff[BUFF_SIZE_HINT] = {0}; 91 | 92 | // Read client requests from the pipe. 93 | bool bFirstRead = true; 94 | BOOL fSuccess = FALSE; 95 | DWORD gle = 0; 96 | do 97 | { 98 | DWORD cbRead = 0; 99 | // Read from the pipe. 100 | fSuccess = ReadFile( 101 | hPipe, // pipe handle 102 | buff, // buffer to receive reply 103 | sizeof(buff), // size of buffer 104 | &cbRead, // number of bytes read 105 | NULL); // not overlapped 106 | 107 | if (!fSuccess && GetLastError() != ERROR_MORE_DATA) 108 | { 109 | gle = GetLastError(); 110 | Log(L"Error reading request from pipe -- stopping service", gle); 111 | bBail = true; 112 | break; 113 | } 114 | 115 | if(bFirstRead) 116 | { 117 | if(cbRead < sizeof(WORD)) 118 | { 119 | gle = GetLastError(); 120 | Log(L"Received too little data in request -- stopping service", gle); 121 | bBail = true; 122 | break; 123 | } 124 | request.SetFromReceivedData(buff, cbRead); 125 | bFirstRead = false; 126 | } 127 | else 128 | request.m_payload.insert(request.m_payload.end(), buff, buff + cbRead); 129 | } while (!fSuccess); // repeat loop if ERROR_MORE_DATA 130 | 131 | if(bBail) 132 | break; 133 | 134 | HandleMsg(request, response, hPipe); 135 | 136 | DWORD totalLen = 0; 137 | const BYTE* pDataToSend = response.GetDataToSend(totalLen); 138 | DWORD cbWritten = 0; 139 | // Send a message to the pipe server. 140 | fSuccess = WriteFile( 141 | hPipe, // pipe handle 142 | pDataToSend, // message 143 | totalLen, // message length 144 | &cbWritten,// bytes written 145 | NULL); // not overlapped 146 | if (!fSuccess || (cbWritten != totalLen)) 147 | { 148 | gle = GetLastError(); 149 | Log(L"Error sending data back -- stopping service.", gle); 150 | bBail = true; 151 | break; 152 | } 153 | } 154 | 155 | // Flush the pipe to allow the client to read the pipe's contents 156 | // before disconnecting. Then disconnect the pipe, and close the 157 | // handle to this pipe instance. 158 | 159 | FlushFileBuffers(hPipe); 160 | DisconnectNamedPipe(hPipe); 161 | CloseHandle(hPipe); 162 | 163 | if(bBail) 164 | ServiceControlHandler(SERVICE_CONTROL_STOP); 165 | 166 | return 0; 167 | } 168 | 169 | VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) 170 | { 171 | gbODS = true; //service always logs to DbgView at first 172 | 173 | Log(L"PAExec ServiceMain starting.", false); 174 | gServiceStatus.dwCurrentState = SERVICE_START_PENDING; 175 | gServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; 176 | gServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; //when this is set, the service name isn't validated since there is only one service running in the process 177 | 178 | if(false == gbDevOnlyDebug) 179 | { 180 | ghService = RegisterServiceCtrlHandler(L"PAExec", ServiceControlHandler); 181 | if(0 == ghService) 182 | { 183 | Log(L"RegisterServiceCtrlHandler failed in PAExec", GetLastError()); 184 | return; 185 | } 186 | 187 | gServiceStatus.dwCurrentState = SERVICE_RUNNING; 188 | BOOL b = SetServiceStatus(ghService, &gServiceStatus); 189 | if(FALSE == b) 190 | Log(L"Failed to signal PAExec running.", GetLastError()); 191 | } 192 | 193 | Log(L"PAExec service running.", false); 194 | 195 | gLastClientContact = _time64(NULL); 196 | 197 | //Pipe server 198 | //the name of the pipe will be the name of the executable, which is based on the caller, so it should always be unique 199 | wchar_t path[_MAX_PATH * 2] = {0}; 200 | GetModuleFileName(NULL, path, ARRAYSIZE(path)); 201 | wchar_t* pC = wcsrchr(path, L'\\'); 202 | if(NULL != pC) 203 | pC++; //get past backslash 204 | CString pipename; 205 | pipename.Format(L"\\\\.\\pipe\\%s", pC); 206 | 207 | // The main loop creates an instance of the named pipe and 208 | // then waits for a client to connect to it. When the client 209 | // connects, a thread is created to handle communications 210 | // with that client, and the loop is repeated. 211 | while(false == gbStop) 212 | { 213 | HANDLE hPipe = CreateNamedPipe( 214 | pipename, // pipe name 215 | PIPE_ACCESS_DUPLEX, // read/write access 216 | PIPE_TYPE_MESSAGE | // message type pipe 217 | PIPE_READMODE_MESSAGE | // message-read mode 218 | PIPE_WAIT, // blocking mode 219 | PIPE_UNLIMITED_INSTANCES, // max. instances 220 | BUFF_SIZE_HINT, // output buffer size 221 | BUFF_SIZE_HINT, // input buffer size 222 | 0, // client time-out 223 | NULL); // default security attribute 224 | 225 | if (BAD_HANDLE(hPipe)) 226 | { 227 | CString msg; 228 | msg.Format(L"PAExec failed to create pipe %s.", pipename); 229 | Log(msg, GetLastError()); 230 | gbStop = true; 231 | continue; 232 | } 233 | else 234 | { 235 | CString msg; 236 | msg.Format(L"PAExec created pipe %s.", pipename); 237 | Log(msg, false); 238 | } 239 | 240 | // Wait for the client to connect; if it succeeds, 241 | // the function returns a nonzero value. If the function 242 | // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 243 | BOOL fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); 244 | 245 | if (fConnected) 246 | { 247 | // Create a thread for this client. 248 | HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, PipeClientThreadProc, hPipe, 0, NULL); 249 | if (hThread == NULL) 250 | { 251 | gbStop = true; 252 | continue; 253 | } 254 | else 255 | CloseHandle(hThread); 256 | } 257 | else 258 | // The client could not connect, so close the pipe. 259 | CloseHandle(hPipe); 260 | } 261 | 262 | Log(L"PAExec exiting loop.", false); 263 | 264 | if(false == gbDevOnlyDebug) 265 | { 266 | gServiceStatus.dwCurrentState = SERVICE_STOPPED; 267 | SetServiceStatus(ghService, &gServiceStatus); 268 | } 269 | } 270 | 271 | DWORD StartLocalService(CCmdLineParser& cmdParser) 272 | { 273 | gbDevOnlyDebug = cmdParser.HasKey(L"dbg"); 274 | gbInService = true; 275 | 276 | SERVICE_TABLE_ENTRY st[] = 277 | { 278 | { L"PAExec", ServiceMain }, //If the service is installed with the SERVICE_WIN32_OWN_PROCESS service type, this member is ignored, but cannot be NULL. This member can be an empty string (""). 279 | { NULL, NULL } 280 | }; 281 | 282 | if(false == gbDevOnlyDebug) 283 | { 284 | if (!::StartServiceCtrlDispatcher(st)) 285 | { 286 | gbODS = true; 287 | Log(L"PAExec failed to start ServiceCtrlDispatcher.", GetLastError()); 288 | return GetLastError(); 289 | } 290 | } 291 | else 292 | { 293 | _ASSERT(0); 294 | ServiceMain(0, NULL); 295 | } 296 | 297 | //for some reason (probably because the service didn't or couldn't stop when requested, and then couldn't be deleted), we're seeing cases where the service definition 298 | //still hangs around (ie a long list of PAExec-xx-yy in services.msc), so here we'll take an additional step and try to delete ourself 299 | SC_HANDLE sch = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); 300 | if(NULL != sch) 301 | { 302 | DWORD myPID = GetCurrentProcessId(); 303 | 304 | BYTE buffer[63 * 1024] = {0}; 305 | DWORD ignored; 306 | DWORD serviceCount = 0; 307 | DWORD resume = 0; 308 | if(EnumServicesStatusEx(sch, SC_ENUM_PROCESS_INFO, SERVICE_WIN32_OWN_PROCESS, SERVICE_STATE_ALL, buffer, sizeof(buffer), &ignored, &serviceCount, &resume, NULL)) 309 | { 310 | ENUM_SERVICE_STATUS_PROCESS* pStruct = (ENUM_SERVICE_STATUS_PROCESS*)buffer; 311 | for(DWORD i = 0; i < serviceCount; i++) 312 | { 313 | if(pStruct[i].ServiceStatusProcess.dwProcessId == myPID) 314 | { 315 | SC_HANDLE hSvc = OpenService(sch, pStruct[i].lpServiceName, SC_MANAGER_ALL_ACCESS); 316 | if(NULL != hSvc) 317 | { 318 | //service should be marked as stopped if we are down here 319 | ::DeleteService(hSvc); 320 | CloseServiceHandle(hSvc); 321 | } 322 | break; 323 | } 324 | } 325 | } 326 | CloseServiceHandle(sch); 327 | } 328 | 329 | return 0; 330 | } 331 | 332 | 333 | -------------------------------------------------------------------------------- /Usage.txt: -------------------------------------------------------------------------------- 1 | PAExec is a freely-redistributable re-implementation of 2 | SysInternal/Microsoft's popular PsExec program. PAExec aims to be a drop 3 | in replacement for PsExec, so the command-line usage is identical, with 4 | additional options also supported. This work was originally inspired by 5 | Talha Tariq's RemCom. 6 | 7 | Usage: PAExec [\\computer[,computer2[,...]] | @file] 8 | [-u user [-p psswd]|[-p@ file [-p@d]]] 9 | [-n s] [-l][-s|-e][-x][-i [session]][-c [-f|-v] [-csrc path]] 10 | [-lo path][-rlo path][-ods][-w directory][-d][-][-a n,n,...] 11 | [-dfr][-noname][-sname name][-share share_name -sharepath share_path] 12 | [-to seconds] cmd [arguments] 13 | 14 | Standard PAExec\PsExec command line options: 15 | 16 | -a Separate processors on which the application can run with 17 | commas where 1 is the lowest numbered CPU. For example, 18 | to run the application on CPU 2 and CPU 4, enter: 19 | -a 2,4 20 | 21 | -c Copy the specified program to the remote system for 22 | execution. If you omit this option the application 23 | must be in the system path on the remote system. 24 | 25 | -d Don't wait for process to terminate (non-interactive). 26 | This option is not compatible with -to 27 | 28 | -e Does not load the specified account's profile. 29 | 30 | -f Copy the specified program even if the file already 31 | exists on the remote system. Requires -c 32 | 33 | -i Run the program so that it interacts with the desktop of the 34 | specified session on the specified system. If no session is 35 | specified the process runs in the console session. 36 | 37 | -h If the target system is Vista or higher, has the process 38 | run with the account's elevated token, if available. 39 | 40 | -l [EXPERIMENTAL] Run process as limited user (strips the 41 | Administrators group and allows only privileges assigned to 42 | the Users group). On Windows Vista the process runs with Low 43 | Integrity. 44 | 45 | -n Specifies timeout in seconds connecting to remote computers. 46 | 47 | -p Specifies optional password for user name. If you omit this 48 | you will be prompted to enter a hidden password. Also see 49 | -p@ and -p@d below. 50 | 51 | -s Run the process in the System account. 52 | 53 | -u Specifies optional user name for login to remote 54 | computer. 55 | 56 | -v Copy the specified file only if it has a higher version number 57 | or is newer than the one on the remote system. Requires -c 58 | 59 | -w Set the working directory of the process (relative to 60 | remote computer). 61 | 62 | -x Display the UI on the Winlogon secure desktop (Local System 63 | only). 64 | 65 | - Specify -low, -belownormal, -abovenormal, -high or 66 | -realtime to run the process at a different priority. Use 67 | -background to run at low memory and I/O priority on Vista. 68 | 69 | computer Direct PAExec to run the application on the remote 70 | computer or computers specified. If you omit the computer 71 | name PAExec runs the application on the local system, 72 | and if you specify a wildcard (\\*), PAExec runs the 73 | command on all computers in the current domain. 74 | 75 | @file PAExec will execute the command on each of the computers 76 | listed in the file. 77 | 78 | program Name of application to execute. 79 | 80 | arguments Arguments to pass (note that file paths must be absolute 81 | paths on the target system). 82 | 83 | Additional options only available in PAExec: 84 | 85 | -cnodel If a file is copied to the server with -c, it is normally 86 | deleted (unless -d is specified). -cnodel indicates the file 87 | should not be deleted. 88 | 89 | -clist When using -c (copy), -clist allows you to specify a text 90 | file that contains a list of files to copy to the target. 91 | The text file should just list file names, and the files 92 | should be in the same folder as the text file. 93 | Example: -c -clist "C:\test path\filelist.txt" 94 | 95 | filelist.txt might contain: 96 | myapp.exe 97 | mydata.dat 98 | 99 | Myapp.exe and mydata.dat would need to be in C:\test path 100 | in the example above. 101 | 102 | IMPORTANT: The first file listed is assumed to be the one that 103 | will be executed. 104 | 105 | -clist and -csrc cannot be used together. 106 | 107 | -csrc When using -c (copy), -csrc allows you to specify an 108 | alternate path to copy the program from. 109 | Example: -c -csrc "C:\test path\file.exe" 110 | 111 | -dbg Output to DebugView application (OutputDebugString) 112 | 113 | -dfr Disable WOW64 File Redirection for the new process 114 | 115 | -lo Log Output to file. Ex: -lo C:\Temp\PAExec.log 116 | The file will be UTF-8 with a Byte Order Mark at the start. 117 | 118 | -p@ Will read the first line of the given file and use that as the 119 | password. File should be saved as UTF-8 with or without 120 | Byte Order Mark. 121 | 122 | -p@d Deletes the file specified by -p@ as soon as the password is 123 | read. 124 | 125 | -rlo Remote Log Output: Log from remote service to file (on remote 126 | server). 127 | Ex: -rlo C:\Temp\PAExec.log 128 | The file will be UTF-8 with a Byte Order Mark at the start. 129 | 130 | -to Timeout in seconds. The launched process must exit within this 131 | number of seconds or it will be terminated. If it is terminated, 132 | the exit code will be -10 133 | This option is not compatible with -d 134 | Ex: -to 15 135 | Terminate the launched process after 15 seconds if it doesn't shut down first 136 | 137 | -noname In order to robustly handle multiple simultaneous connections to a server, 138 | the source server's name is added to the remote service name and remote PAExec 139 | executable file. If you do NOT want this behavior, use -noname 140 | 141 | -sname Remote service name. 142 | This option is not compatible with -noname 143 | 144 | -share Remote share name. 145 | -sharepath must be supplied when using this functionality 146 | 147 | -sharepath Real path of specified remote share. 148 | -sharepath can only be used with -share 149 | 150 | The application name, copy source, working directory and log file 151 | entries can be quoted if the path contains a space. For example: 152 | PAExec \\test-server -w "C:\path with space" "C:\program files\app.exe" 153 | 154 | Like PsExec, input is sent to the remote system when Enter is pressed, 155 | and Ctrl-C stops the remote process and stops PAExec. 156 | 157 | PsExec passes all parameters in clear-text. In contrast, PAExec will scramble 158 | the parameters to protect them from casual wire sniffers, but they are NOT 159 | encrypted. Note that data passed between PAExec and the remote program is NOT 160 | scrambled or encrypted -- the same as with PsExec. 161 | 162 | PAExec will return the error code it receives from the application that was 163 | launched remotely. If PAExec itself has an error, the return code will be 164 | one of: 165 | 166 | -1 = internal error 167 | -2 = command line error 168 | -3 = failed to launch app (locally) 169 | -4 = failed to copy PAExec to remote (connection to ADMIN$ might have 170 | failed) 171 | -5 = connection to server taking too long (timeout) 172 | -6 = PAExec service could not be installed/started on remote server 173 | -7 = could not communicate with remote PAExec service 174 | -8 = failed to copy app to remote server 175 | -9 = failed to launch app (remotely) 176 | -10 = app was terminated after timeout expired 177 | -11 = forcibly stopped with Ctrl-C / Ctrl-Break 178 | -------------------------------------------------------------------------------- /UtilityFuncs.cpp: -------------------------------------------------------------------------------- 1 | // UtilityFuncs.cpp: Helper functions 2 | // 3 | // Copyright (c) Power Admin LLC, 2012 - 2013 4 | // 5 | // This code is provided "as is", with absolutely no warranty expressed 6 | // or implied. Any use is at your own risk. 7 | // 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | #include "stdafx.h" 11 | #include "PAExec.h" 12 | 13 | extern bool gbODS; 14 | extern CString gLogPath; 15 | 16 | static CString gLastLog; 17 | 18 | CString LastLog() 19 | { 20 | return gLastLog; 21 | } 22 | 23 | CString StrFormat(LPCTSTR pFormat, ...) //returns a formatted CString. Inspired by .NET's String.Format 24 | { 25 | CString result; 26 | DWORD size = max(4096, (DWORD)_tcslen(pFormat) + 4096); 27 | 28 | _ASSERT(NULL == wcsstr(pFormat, L"{0}")); //StrFormat2 should have been used?? GLOK 29 | 30 | try 31 | { 32 | while(true) 33 | { 34 | va_list pArg; 35 | va_start(pArg, pFormat); 36 | int res = _vsntprintf_s(result.GetBuffer(size), size, _TRUNCATE, pFormat, pArg); 37 | va_end(pArg); 38 | if(res >= 0) 39 | { 40 | result.ReleaseBuffer(); 41 | return result; 42 | } 43 | else 44 | { 45 | result.ReleaseBuffer(1); 46 | size += 8192; 47 | if(size > (12 * 1024 * 1024)) 48 | { 49 | _ASSERT(0); 50 | Log(StrFormat(L"STRING TOO LONG: %s", pFormat), true); 51 | CString s = L""; //GLOK 54 | return s; 55 | } 56 | } 57 | } 58 | } 59 | catch(...) 60 | { 61 | _ASSERT(0); 62 | CString res = L""; //GLOK 68 | res.Replace(L'%', L'{'); //so no further formatting is attempted GLOK 69 | return res; 70 | } 71 | } 72 | 73 | void Log(LPCWSTR str, DWORD lastError) 74 | { 75 | CString s = str; 76 | s += L" "; 77 | s += GetSystemErrorMessage(lastError); 78 | Log(s, 0 != lastError); 79 | } 80 | 81 | void Log(LPCWSTR str, bool bForceODS) 82 | { 83 | CString s = str; 84 | s += L"\r\n"; 85 | 86 | HANDLE oh = GetStdHandle( STD_OUTPUT_HANDLE ); 87 | DWORD ignored; 88 | WriteConsole(oh, (LPCWSTR)s, wcslen(s), &ignored, NULL); 89 | 90 | if(gbODS || bForceODS) 91 | OutputDebugString(s); 92 | 93 | if(FALSE == gLogPath.IsEmpty()) 94 | { 95 | HANDLE hf = CreateFile(gLogPath, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0); 96 | if(!BAD_HANDLE(hf)) 97 | { 98 | DWORD ignored; 99 | ULARGE_INTEGER ui = {0}; 100 | ui.LowPart = GetFileSize(hf, &ui.HighPart); 101 | if(0 == ui.QuadPart) 102 | WriteFile(hf, &UTF8_BOM, sizeof(UTF8_BOM), &ignored, 0); 103 | SetFilePointer(hf, 0, 0, FILE_END); 104 | 105 | //now convert to UTF-8 106 | DWORD len = WideCharToMultiByte(CP_UTF8, 0, s, wcslen(s), NULL, 0, NULL, NULL); 107 | if(0 != len) 108 | { 109 | char* pBuff = new char[len + 5]; 110 | WideCharToMultiByte(CP_UTF8, 0, s, wcslen(s), pBuff, len, NULL, NULL); 111 | pBuff[len] = '\0'; 112 | WriteFile(hf, pBuff, len, &ignored, 0); 113 | delete [] pBuff; 114 | } 115 | FlushFileBuffers(hf); 116 | CloseHandle(hf); 117 | } 118 | } 119 | gLastLog = s; 120 | } 121 | 122 | DWORD wtodw(const wchar_t* num) 123 | { 124 | if(NULL == num) 125 | return 0; 126 | //in VS2003 atoi/wtoi would convert an unsigned value even though it would overflow a signed value. No more, and it causes trouble! 127 | __int64 res = _wtoi64(num); 128 | _ASSERT(res <= ULONG_MAX); 129 | return (DWORD)res; 130 | } 131 | 132 | CString GetSystemErrorMessage(DWORD lastErrorVal) 133 | { 134 | if(0 == lastErrorVal) 135 | return L" OK [Ret=0]"; 136 | 137 | CString osErr = _T("Unknown error value. "); 138 | const int errSize = 8192; 139 | HMODULE hMod = NULL; 140 | DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; 141 | DWORD len = FormatMessage(flags, hMod, lastErrorVal, 0, osErr.GetBuffer(errSize), errSize - 50, NULL); 142 | osErr.ReleaseBuffer(); 143 | osErr.Replace(L"\r", L""); 144 | osErr.Replace(L"\n", L""); 145 | osErr += StrFormat(L" [Err=0x%0X, %u]", lastErrorVal, lastErrorVal); 146 | 147 | return osErr; 148 | } 149 | 150 | 151 | bool EnablePrivilege(LPCWSTR privilegeStr, HANDLE hToken /* = NULL */) 152 | { 153 | TOKEN_PRIVILEGES tp; // token privileges 154 | LUID luid; 155 | bool bCloseToken = false; 156 | 157 | if(NULL == hToken) 158 | { 159 | if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 160 | { 161 | Log(StrFormat(L"Failed to open process to enable privilege %s", privilegeStr), false); 162 | return false; 163 | } 164 | bCloseToken = true; 165 | } 166 | 167 | if (!LookupPrivilegeValue(NULL, privilegeStr, &luid)) 168 | { 169 | if(bCloseToken) 170 | CloseHandle (hToken); 171 | _ASSERT(0); 172 | Log(StrFormat(L"Could not find privilege %s", privilegeStr), false); 173 | return false; 174 | } 175 | 176 | ZeroMemory (&tp, sizeof (tp)); 177 | tp.PrivilegeCount = 1; 178 | tp.Privileges[0].Luid = luid; 179 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 180 | 181 | // Adjust Token privileges 182 | if (!AdjustTokenPrivileges (hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) 183 | { 184 | DWORD gle = GetLastError(); 185 | Log(StrFormat(L"Failed to adjust token for privilege %s", privilegeStr), gle); 186 | if(bCloseToken) 187 | CloseHandle (hToken); 188 | _ASSERT(0); 189 | return false; 190 | } 191 | if(bCloseToken) 192 | CloseHandle (hToken); 193 | 194 | return true; 195 | } 196 | 197 | 198 | 199 | RemMsg& RemMsg::operator<<(LPCWSTR s) 200 | { 201 | *this << (DWORD)wcslen(s); 202 | m_payload.insert(m_payload.end(), (BYTE*)s, (BYTE*)(s + wcslen(s))); 203 | return *this; 204 | } 205 | 206 | RemMsg& RemMsg::operator<<(bool b) 207 | { 208 | m_payload.insert(m_payload.end(), (BYTE*)&b, ((BYTE*)&b) + sizeof(b)); 209 | return *this; 210 | } 211 | 212 | RemMsg& RemMsg::operator<<(DWORD d) 213 | { 214 | m_payload.insert(m_payload.end(), (BYTE*)&d, ((BYTE*)&d) + sizeof(d)); 215 | return *this; 216 | } 217 | 218 | RemMsg& RemMsg::operator<<(__int64 d) 219 | { 220 | m_payload.insert(m_payload.end(), (BYTE*)&d, ((BYTE*)&d) + sizeof(d)); 221 | return *this; 222 | } 223 | 224 | RemMsg& RemMsg::operator<<(FILETIME d) 225 | { 226 | m_payload.insert(m_payload.end(), (BYTE*)&d, ((BYTE*)&d) + sizeof(d)); 227 | return *this; 228 | } 229 | 230 | RemMsg& RemMsg::operator>>(CString& s) 231 | { 232 | if(m_bResetReadItr) 233 | { 234 | m_readItr = m_payload.begin(); 235 | m_bResetReadItr = false; 236 | } 237 | 238 | s.Empty(); 239 | DWORD len = 0; 240 | *this >> len; //this is len in wchar_t 241 | if(0 != len) 242 | { 243 | if(m_readItr >= m_payload.end()) 244 | return *this; 245 | 246 | LPWSTR pStart = s.GetBuffer(len); 247 | memcpy(pStart, &(*m_readItr), len * sizeof(wchar_t)); 248 | s.ReleaseBuffer(len); 249 | m_readItr += len * sizeof(wchar_t); 250 | } 251 | 252 | return *this; 253 | } 254 | 255 | RemMsg& RemMsg::operator>>(bool& b) 256 | { 257 | if(m_bResetReadItr) 258 | { 259 | m_readItr = m_payload.begin(); 260 | m_bResetReadItr = false; 261 | } 262 | 263 | if(m_readItr >= m_payload.end()) 264 | return *this; 265 | 266 | memcpy(&b, &(*m_readItr), sizeof(b)); 267 | m_readItr += sizeof(b); 268 | 269 | return *this; 270 | 271 | } 272 | 273 | RemMsg& RemMsg::operator>>(DWORD& d) 274 | { 275 | if(m_bResetReadItr) 276 | { 277 | m_readItr = m_payload.begin(); 278 | m_bResetReadItr = false; 279 | } 280 | 281 | if(m_readItr >= m_payload.end()) 282 | return *this; 283 | 284 | memcpy(&d, &(*m_readItr), sizeof(d)); 285 | m_readItr += sizeof(d); 286 | 287 | return *this; 288 | } 289 | 290 | RemMsg& RemMsg::operator>>(__int64& d) 291 | { 292 | if(m_bResetReadItr) 293 | { 294 | m_readItr = m_payload.begin(); 295 | m_bResetReadItr = false; 296 | } 297 | 298 | if(m_readItr >= m_payload.end()) 299 | return *this; 300 | 301 | memcpy(&d, &(*m_readItr), sizeof(d)); 302 | m_readItr += sizeof(d); 303 | 304 | return *this; 305 | } 306 | 307 | 308 | RemMsg& RemMsg::operator>>(FILETIME& d) 309 | { 310 | if(m_bResetReadItr) 311 | { 312 | m_readItr = m_payload.begin(); 313 | m_bResetReadItr = false; 314 | } 315 | 316 | if(m_readItr >= m_payload.end()) 317 | return *this; 318 | 319 | memcpy(&d, &(*m_readItr), sizeof(d)); 320 | m_readItr += sizeof(d); 321 | 322 | return *this; 323 | } 324 | 325 | 326 | 327 | typedef BOOL (WINAPI *Wow64DisableWow64FsRedirectionProc)(PVOID* OldValue); 328 | typedef BOOL (WINAPI *Wow64RevertWow64FsRedirectionProc)(PVOID OldValue); 329 | 330 | static HMODULE hKernel = NULL; 331 | static Wow64DisableWow64FsRedirectionProc pDis = NULL; 332 | static Wow64RevertWow64FsRedirectionProc pRev = NULL; 333 | PVOID OldWow64RedirVal = NULL; 334 | 335 | void DisableFileRedirection() 336 | { 337 | if(NULL == hKernel) 338 | hKernel = LoadLibrary(_T("Kernel32.dll")); 339 | 340 | if((NULL != hKernel) && ((NULL == pDis) || (NULL == pRev))) 341 | { 342 | pDis = (Wow64DisableWow64FsRedirectionProc)GetProcAddress(hKernel, "Wow64DisableWow64FsRedirection"); 343 | pRev = (Wow64RevertWow64FsRedirectionProc)GetProcAddress(hKernel, "Wow64RevertWow64FsRedirection"); 344 | } 345 | 346 | if(NULL != pDis) 347 | { 348 | BOOL b = pDis(&OldWow64RedirVal); 349 | if(b) 350 | Log(L"Disabled WOW64 file system redirection", false); 351 | else 352 | Log(L"Failed to disable WOW64 file system redirection", GetLastError()); 353 | } 354 | else 355 | Log(L"Failed to find Wow64DisableWow64FsRedirection API", true); 356 | } 357 | 358 | void RevertFileRedirection() 359 | { 360 | if(NULL == hKernel) 361 | hKernel = LoadLibrary(_T("Kernel32.dll")); 362 | 363 | if((NULL != hKernel) && ((NULL == pDis) || (NULL == pRev))) 364 | { 365 | pDis = (Wow64DisableWow64FsRedirectionProc)GetProcAddress(hKernel, "Wow64DisableWow64FsRedirection"); 366 | pRev = (Wow64RevertWow64FsRedirectionProc)GetProcAddress(hKernel, "Wow64RevertWow64FsRedirection"); 367 | } 368 | 369 | if(NULL != pRev) 370 | pRev(OldWow64RedirVal); 371 | } 372 | 373 | CString ExpandToFullPath(LPCWSTR path) 374 | { 375 | CString result = path; 376 | 377 | wchar_t found[_MAX_PATH * 2] = {0}; 378 | LPWSTR ignored; 379 | if(0 != SearchPath(NULL, path, NULL, sizeof(found)/sizeof(wchar_t), found, &ignored)) 380 | result = found; 381 | 382 | return result; 383 | } 384 | 385 | 386 | bool ReadTextFile(LPCWSTR fileName, CString& content) 387 | { 388 | content.Empty(); 389 | 390 | HANDLE hf = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); 391 | DWORD gle = GetLastError(); 392 | if(!BAD_HANDLE(hf)) 393 | { 394 | //read in UTF-8 395 | DWORD ignored; 396 | DWORD size = GetFileSize(hf, &ignored); 397 | BYTE* pOrig = NULL; 398 | BYTE* pBuff = pOrig = new BYTE[size + 1]; 399 | DWORD read = 0; 400 | ReadFile(hf, pBuff, size, &read, 0); 401 | pBuff[read] = '\0'; 402 | if(0 == memcmp(pBuff, UTF8_BOM, sizeof(UTF8_BOM))) 403 | { 404 | pBuff += sizeof(UTF8_BOM); 405 | read -= sizeof(UTF8_BOM); 406 | } 407 | 408 | //now convert to unicode 409 | DWORD len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pBuff, read, NULL, 0); 410 | if(0 == len) 411 | { 412 | gle = GetLastError(); 413 | _ASSERT(0); 414 | Log(StrFormat(L"Failed to translate UTF-8 file %s into Unicode.", fileName), gle); 415 | return false; 416 | } 417 | else 418 | { 419 | LPWSTR output = content.GetBuffer(len + 1); 420 | len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pBuff, read, output, len); 421 | content.ReleaseBuffer(len); 422 | } 423 | 424 | delete [] pOrig; 425 | pOrig = NULL; 426 | pBuff = NULL; 427 | CloseHandle(hf); 428 | 429 | return true; 430 | } 431 | else 432 | { 433 | Log(StrFormat(L"Failed to open text file %s.", fileName), gle); 434 | return false; 435 | } 436 | } 437 | 438 | 439 | void SplitUserNameAndDomain(CString user, CString& username, CString& domain) 440 | { 441 | //defaults 442 | username = user; 443 | domain = L""; 444 | 445 | int idx = user.Find(L"\\"); 446 | if (idx != -1) 447 | { 448 | domain = user.Left(idx); 449 | username = user.Right(user.GetLength() - (idx + 1)); 450 | } 451 | else 452 | { 453 | idx = user.Find(L"@"); 454 | if (idx != -1) 455 | { 456 | username = user.Left(idx); 457 | domain = user.Right(user.GetLength() - (idx + 1)); 458 | } 459 | } 460 | } 461 | 462 | // Strips the domain name from an user name string, if given as domain\user or user@domain 463 | CString RemoveDomainFromUserName(CString user) 464 | { 465 | CString ret = user; 466 | int idx = user.Find(L"\\"); 467 | if (idx != -1) 468 | { 469 | ret.Delete(0, idx + 1); 470 | } 471 | else 472 | { 473 | idx = user.Find(L"@"); 474 | if (idx != -1) 475 | ret = ret.Left(idx); 476 | } 477 | return ret; 478 | } 479 | 480 | #ifdef _DEBUG 481 | 482 | bool ReportAssert(LPCWSTR file, int line, LPCWSTR expr) 483 | { 484 | CString msg = L"ASSERT: "; 485 | if(NULL != expr) 486 | msg += expr; 487 | msg += StrFormat(L" (%s, %d)", file, line); 488 | msg.Replace(L"\r", L""); 489 | msg.Replace(L"\n", L""); 490 | msg += L"\r\n"; 491 | 492 | OutputDebugString(msg); 493 | 494 | if(gbInService) 495 | return true; //continue 496 | 497 | return 1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, expr); 498 | } 499 | 500 | #endif 501 | -------------------------------------------------------------------------------- /regression1.txt: -------------------------------------------------------------------------------- 1 | paexec.exe 2 | paexec.obj 3 | paexec.pdb 4 | -------------------------------------------------------------------------------- /regression2.txt: -------------------------------------------------------------------------------- 1 | paexec.exe 2 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by PAExec.rc 4 | // 5 | #define IDR_TEXT1 101 6 | #define IDS_APP_TITLE 103 7 | 8 | // Next default values for new objects 9 | // 10 | #ifdef APSTUDIO_INVOKED 11 | #ifndef APSTUDIO_READONLY_SYMBOLS 12 | #define _APS_NEXT_RESOURCE_VALUE 102 13 | #define _APS_NEXT_COMMAND_VALUE 40001 14 | #define _APS_NEXT_CONTROL_VALUE 1000 15 | #define _APS_NEXT_SYMED_VALUE 101 16 | #endif 17 | #endif 18 | -------------------------------------------------------------------------------- /stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // PAExec.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #define _CRT_SECURE_NO_WARNINGS 9 | #define _CRT_NON_CONFORMING_WCSTOK 10 | 11 | #include "targetver.h" 12 | 13 | #define _CRT_RAND_S 14 | #include 15 | #include 16 | #include 17 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit 18 | #include 19 | #include 20 | 21 | #ifdef _DEBUG 22 | #define VERIFY(x) { BOOL b = x; _ASSERT(b); } 23 | #else 24 | #define VERIFY(x) x 25 | #endif 26 | 27 | #ifndef VC_EXTRALEAN 28 | #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers 29 | #endif 30 | 31 | //#include 32 | //#include // MFC core and standard components 33 | //#include // MFC extensions 34 | //#ifndef _AFX_NO_OLE_SUPPORT 35 | //#include // MFC support for Internet Explorer 4 Common Controls 36 | //#endif 37 | //#ifndef _AFX_NO_AFXCMN_SUPPORT 38 | //#include // MFC support for Windows Common Controls 39 | //#endif // _AFX_NO_AFXCMN_SUPPORT 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "CmdLineParser.h" 46 | 47 | extern bool gbODS; 48 | extern bool gbStop; 49 | extern bool gbInService; 50 | 51 | #define BAD_HANDLE(x) ((INVALID_HANDLE_VALUE == x) || (NULL == x)) 52 | 53 | const WORD MSGID_SETTINGS = 1; //local sending settings to remote service, content of this message scrambled 54 | const WORD MSGID_RESP_SEND_FILES = 2; //remote indicates file needs to be sent 55 | const WORD MSGID_SENT_FILES = 3; //local is sending file 56 | const WORD MSGID_OK = 4; //simple OK response 57 | const WORD MSGID_START_APP = 5; 58 | const WORD MSGID_FAILED = 6; //request failed 59 | 60 | class FileInfo 61 | { 62 | public: 63 | FileInfo() { bCopyFile = false; fileVersionMS = 0; fileVersionLS = 0; fileLastWrite.dwHighDateTime = 0; fileLastWrite.dwLowDateTime = 0; } 64 | FileInfo(const FileInfo& in) { *this = in; } 65 | FileInfo& operator=(const FileInfo& in) 66 | { 67 | filenameOnly = in.filenameOnly; 68 | fullFilePath = in.fullFilePath; 69 | bCopyFile = in.bCopyFile; 70 | fileVersionMS = in.fileVersionMS; 71 | fileVersionLS = in.fileVersionLS; 72 | fileLastWrite.dwHighDateTime = in.fileLastWrite.dwHighDateTime; 73 | fileLastWrite.dwLowDateTime = in.fileLastWrite.dwLowDateTime; 74 | return *this; 75 | } 76 | 77 | CString filenameOnly; 78 | CString fullFilePath; //not serialized to remote server 79 | FILETIME fileLastWrite; 80 | DWORD fileVersionMS; 81 | DWORD fileVersionLS; 82 | bool bCopyFile; //not serialized to remote server 83 | }; 84 | 85 | class RemMsg 86 | { 87 | RemMsg(const RemMsg&); //not implemented 88 | RemMsg& operator=(const RemMsg&); //not implemented 89 | 90 | DWORD GetUniqueID(); 91 | 92 | public: 93 | RemMsg() { m_msgID = 0; m_pBuff = NULL; m_expectedLen = 0; m_uniqueProcessID = GetUniqueID(); } 94 | RemMsg(WORD msgID) 95 | { 96 | m_msgID = msgID; 97 | m_bResetReadItr = true; 98 | m_pBuff = NULL; 99 | m_expectedLen = 0; 100 | m_uniqueProcessID = GetUniqueID(); 101 | } 102 | virtual ~RemMsg() 103 | { 104 | delete [] m_pBuff; 105 | } 106 | 107 | const BYTE* GetDataToSend(DWORD& totalLen); 108 | void SetFromReceivedData(BYTE* pData, DWORD dataLen); //returns whether all data received or not 109 | 110 | RemMsg& operator<<(LPCWSTR s); 111 | RemMsg& operator<<(bool b); 112 | RemMsg& operator<<(DWORD d); 113 | RemMsg& operator<<(__int64 d); 114 | RemMsg& operator<<(FILETIME d); 115 | 116 | RemMsg& operator>>(CString& s); 117 | RemMsg& operator>>(bool& b); 118 | RemMsg& operator>>(DWORD& d); 119 | RemMsg& operator>>(__int64& d); 120 | RemMsg& operator>>(FILETIME& d); 121 | 122 | WORD m_msgID; 123 | std::vector m_payload; 124 | std::vector::iterator m_readItr; 125 | bool m_bResetReadItr; 126 | DWORD m_expectedLen; 127 | DWORD m_uniqueProcessID; //helps remote PAExecs differentiate from possibly multiple clients talking to it 128 | 129 | private: 130 | BYTE* m_pBuff; 131 | }; 132 | 133 | class Settings 134 | { 135 | Settings(const Settings&); //not implemented 136 | Settings& operator=(const Settings&); //not implemented 137 | public: 138 | Settings() 139 | { 140 | bCopyFiles = false; 141 | bForceCopy = false; 142 | bCopyIfNewerOrHigherVer = false; 143 | bDontWaitForTerminate = false; 144 | bDontLoadProfile = false; 145 | bRunElevated = false; 146 | bRunLimited = false; 147 | remoteCompConnectTimeoutSec = 0; 148 | bUseSystemAccount = false; 149 | bShowUIOnWinLogon = false; 150 | priority = NORMAL_PRIORITY_CLASS; 151 | hProcess = INVALID_HANDLE_VALUE; 152 | hUserProfile = INVALID_HANDLE_VALUE; //call UnloadUserProfile when done 153 | hUser = INVALID_HANDLE_VALUE; 154 | hUserImpersonated = INVALID_HANDLE_VALUE; 155 | bDisableFileRedirection = false; 156 | bODS = false; 157 | hStdOut = INVALID_HANDLE_VALUE; 158 | hStdIn = INVALID_HANDLE_VALUE; 159 | hStdErr = INVALID_HANDLE_VALUE; 160 | bNeedToDetachFromAdmin = false; 161 | bNeedToDetachFromIPC = false; 162 | bNeedToDeleteServiceFile = false; 163 | bNeedToDeleteService = false; 164 | bInteractive = false; 165 | processID = 0; 166 | bNoDelete = false; 167 | timeoutSeconds = 0; 168 | bNoName = false; 169 | sessionToInteractWith = (DWORD)-1; //not initialized 170 | targetShare = L"ADMIN$"; 171 | targetSharePath = L"%SYSTEMROOT%"; 172 | } 173 | 174 | void Serialize(RemMsg& msg, bool bSave) 175 | { 176 | #define CUR_SERIALIZE_VERSION 1 177 | if(bSave) 178 | { 179 | msg << (DWORD)CUR_SERIALIZE_VERSION; 180 | msg << (DWORD)allowedProcessors.size(); 181 | for(std::vector::iterator itr = allowedProcessors.begin(); allowedProcessors.end() != itr; itr++) 182 | msg << (DWORD)*itr; 183 | msg << bCopyFiles; 184 | msg << bForceCopy; 185 | msg << bCopyIfNewerOrHigherVer; 186 | msg << bDontWaitForTerminate; 187 | msg << bDontLoadProfile; 188 | msg << sessionToInteractWith; 189 | msg << bInteractive; 190 | msg << bRunElevated; 191 | msg << bRunLimited; 192 | msg << password; 193 | msg << user; 194 | msg << bUseSystemAccount; 195 | msg << workingDir; 196 | msg << bShowUIOnWinLogon; 197 | msg << (DWORD)priority; 198 | msg << app; 199 | msg << appArgs; 200 | msg << bDisableFileRedirection; 201 | msg << bODS; 202 | msg << remoteLogPath; 203 | msg << bNoDelete; 204 | msg << srcDir; 205 | msg << destDir; 206 | msg << (DWORD)srcFileInfos.size(); 207 | for(std::vector::iterator fItr = srcFileInfos.begin(); srcFileInfos.end() != fItr; fItr++) 208 | { 209 | msg << (*fItr).filenameOnly; 210 | msg << (*fItr).fileLastWrite; 211 | msg << (*fItr).fileVersionLS; 212 | msg << (*fItr).fileVersionMS; 213 | //bCopyFiles is NOT sent 214 | //fullFilePath is NOT sent 215 | } 216 | msg << (DWORD)destFileInfos.size(); 217 | for(std::vector::iterator fItr = destFileInfos.begin(); destFileInfos.end() != fItr; fItr++) 218 | { 219 | msg << (*fItr).filenameOnly; 220 | msg << (*fItr).fileLastWrite; 221 | msg << (*fItr).fileVersionLS; 222 | msg << (*fItr).fileVersionMS; 223 | //bCopyFiles is NOT sent 224 | //fullFilePath is NOT sent 225 | } 226 | msg << timeoutSeconds; 227 | } 228 | else 229 | { 230 | allowedProcessors.clear(); 231 | 232 | DWORD version = 0; 233 | msg >> version; 234 | 235 | DWORD num = 0; 236 | msg >> num; 237 | for(DWORD i = 0; i < num; i++) 238 | { 239 | DWORD proc = 0; 240 | msg >> proc; 241 | allowedProcessors.push_back((WORD)proc); 242 | } 243 | msg >> bCopyFiles; 244 | msg >> bForceCopy; 245 | msg >> bCopyIfNewerOrHigherVer; 246 | msg >> bDontWaitForTerminate; 247 | msg >> bDontLoadProfile; 248 | msg >> sessionToInteractWith; 249 | msg >> bInteractive; 250 | msg >> bRunElevated; 251 | msg >> bRunLimited; 252 | msg >> password; 253 | msg >> user; 254 | msg >> bUseSystemAccount; 255 | msg >> workingDir; 256 | msg >> bShowUIOnWinLogon; 257 | msg >> (DWORD&)priority; 258 | msg >> app; 259 | msg >> appArgs; 260 | msg >> bDisableFileRedirection; 261 | msg >> bODS; 262 | msg >> remoteLogPath; 263 | msg >> bNoDelete; 264 | msg >> srcDir; 265 | msg >> destDir; 266 | 267 | msg >> num; 268 | for(DWORD i = 0; i < num; i++) 269 | { 270 | FileInfo fi; 271 | msg >> fi.filenameOnly; 272 | msg >> fi.fileLastWrite; 273 | msg >> fi.fileVersionLS; 274 | msg >> fi.fileVersionMS; 275 | fi.bCopyFile = false; //not known whether it will be copied yet 276 | //bCopyFiles is NOT sent 277 | //fullFilePath is NOT sent 278 | srcFileInfos.push_back(fi); 279 | } 280 | 281 | msg >> num; 282 | for(DWORD i = 0; i < num; i++) 283 | { 284 | FileInfo fi; 285 | msg >> fi.filenameOnly; 286 | msg >> fi.fileLastWrite; 287 | msg >> fi.fileVersionLS; 288 | msg >> fi.fileVersionMS; 289 | fi.bCopyFile = false; //not known whether it will be copied yet 290 | //bCopyFiles is NOT sent 291 | //fullFilePath is NOT sent 292 | destFileInfos.push_back(fi); 293 | } 294 | msg >> timeoutSeconds; 295 | } 296 | } 297 | 298 | bool ResolveFilePaths(); 299 | 300 | std::vector allowedProcessors; //empty means any 301 | bool bCopyFiles; 302 | bool bForceCopy; 303 | bool bCopyIfNewerOrHigherVer; 304 | bool bDontWaitForTerminate; 305 | bool bDontLoadProfile; 306 | DWORD sessionToInteractWith; 307 | bool bInteractive; 308 | bool bRunElevated; 309 | bool bRunLimited; 310 | CString password; 311 | CString user; 312 | bool bUseSystemAccount; 313 | CString workingDir; 314 | bool bShowUIOnWinLogon; 315 | int priority; 316 | CString app; 317 | CString appArgs; 318 | bool bDisableFileRedirection; 319 | bool bODS; 320 | CString remoteLogPath; 321 | bool bNoDelete; 322 | CString srcDir; 323 | CString destDir; 324 | std::vector srcFileInfos; //index 0 will be source of app, but may not match app if -csrc or -clist are being used 325 | std::vector destFileInfos; //index 0 will be app 326 | DWORD timeoutSeconds; 327 | 328 | //NOT TRANSMITTED 329 | DWORD remoteCompConnectTimeoutSec; 330 | std::vector computerList; //run locally if empty 331 | HANDLE hProcess; 332 | DWORD processID; 333 | HANDLE hUserProfile; //call UnloadUserProfile when done 334 | HANDLE hUser; 335 | HANDLE hUserImpersonated; 336 | CString localLogPath; 337 | HANDLE hStdOut; 338 | HANDLE hStdIn; 339 | HANDLE hStdErr; 340 | bool bNeedToDetachFromAdmin; 341 | bool bNeedToDetachFromIPC; 342 | bool bNeedToDeleteServiceFile; 343 | bool bNeedToDeleteService; 344 | bool bNoName; 345 | CString serviceName; 346 | CString targetShare; 347 | CString targetSharePath; 348 | }; 349 | 350 | class ListenParam 351 | { 352 | public: 353 | ListenParam() { pSettings = NULL; remoteServer = NULL; pid = GetCurrentProcessId(); hStop = CreateEvent(NULL, TRUE, FALSE, NULL); workerThreads = 0; InitializeCriticalSection(&cs); } 354 | ~ListenParam() { CloseHandle(hStop); DeleteCriticalSection(&cs); } 355 | Settings* pSettings; 356 | LPCWSTR remoteServer; 357 | CString machineName; 358 | DWORD pid; 359 | HANDLE hStop; 360 | long workerThreads; 361 | CRITICAL_SECTION cs; 362 | std::vector inputSentToSuppressInOutput; 363 | }; 364 | 365 | const unsigned char UTF8_BOM[] = { unsigned char(0xEF), unsigned char(0xBB), unsigned char(0xBF) }; 366 | 367 | 368 | typedef struct 369 | { 370 | DWORD origSessionID; 371 | HANDLE hUser; 372 | bool bPreped; 373 | }CleanupInteractive; 374 | 375 | 376 | DWORD StartLocalService(CCmdLineParser& cmdParser); 377 | void Log(LPCWSTR str, bool bForceODS); 378 | void Log(LPCWSTR str, DWORD lastError); 379 | CString GetSystemErrorMessage(DWORD lastErrorVal); 380 | CString StrFormat(LPCTSTR pFormat, ...); //returns a formatted CString. Inspired by .NET's String.Format 381 | void PrintCopyright(); 382 | void PrintUsage(); 383 | bool GetComputerList(Settings& settings, LPCWSTR& cmdLine); 384 | DWORD wtodw(const wchar_t* num); 385 | bool StartProcess(Settings& settings, HANDLE remoteCmdPipe); 386 | bool EnablePrivilege(LPCWSTR privilegeStr, HANDLE hToken = NULL); 387 | BOOL PrepForInteractiveProcess(Settings& settings, CleanupInteractive* pCI); 388 | void CleanUpInteractiveProcess(CleanupInteractive* pCI); 389 | bool EstablishConnection(Settings& settings, LPCTSTR lpszRemote, LPCTSTR lpszResource, bool bConnect); 390 | bool CopyPAExecToRemote(Settings& settings, LPCWSTR targetComputer); 391 | bool InstallAndStartRemoteService(LPCWSTR remoteServer, Settings& settings); 392 | bool SendSettings(LPCWSTR remoteServer, Settings& settings, HANDLE& hPipe, bool& bNeedToSendFile); 393 | bool SendRequest(LPCWSTR remoteServer, HANDLE& hPipe, RemMsg& msgOut, RemMsg& msgReturned, Settings& settings); 394 | bool SendFilesToRemote(LPCWSTR remoteServer, Settings& settings, HANDLE& hPipe); 395 | void StopAndDeleteRemoteService(LPCWSTR remoteServer, Settings& settings); 396 | void DeletePAExecFromRemote(LPCWSTR targetComputer, Settings& settings); 397 | void HandleMsg(RemMsg& msg, RemMsg& response, HANDLE hPipe); 398 | bool GetTargetFileInfo(Settings& settings); 399 | bool ParseCommandLine(Settings& settings, LPCWSTR cmdLine); 400 | void StartRemoteApp(LPCWSTR remoteServer, Settings& settings, HANDLE& hPipe, int& appExitCode); 401 | void DisableFileRedirection(); 402 | void RevertFileRedirection(); 403 | bool CreateIOPipesInService(Settings& settings, LPCWSTR caller, DWORD pid); 404 | BOOL ConnectToRemotePipes(ListenParam* pLP, DWORD dwRetryCount, DWORD dwRetryTimeOut); 405 | CString LastLog(); 406 | void DuplicateTokenToIncreaseRights(HANDLE& h, LPCSTR file, int line); 407 | bool ReadTextFile(LPCWSTR fileName, CString& content); 408 | CString ExpandToFullPath(LPCWSTR path); 409 | BOOL IsLocalSystem(); 410 | 411 | -------------------------------------------------------------------------------- /targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // The following macros define the minimum required platform. The minimum required platform 4 | // is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 5 | // your application. The macros work by enabling all features available on platform versions up to and 6 | // including the version specified. 7 | 8 | // Modify the following defines if you have to target a platform prior to the ones specified below. 9 | // Refer to MSDN for the latest info on corresponding values for different platforms. 10 | 11 | #ifndef WINVER 12 | #define WINVER 0x0501 //WinXP or newer 13 | #endif 14 | 15 | #ifndef _WIN32_WINNT 16 | #define _WIN32_WINNT 0x0501 17 | #endif 18 | 19 | #ifndef _WIN32_WINDOWS 20 | #define _WIN32_WINDOWS 0x0501 21 | #endif 22 | 23 | #ifndef _WIN32_IE // Specifies that the minimum required platform is Internet Explorer 7.0. 24 | #define _WIN32_IE 0x0700 // Change this to the appropriate value to target other versions of IE. 25 | #endif 26 | --------------------------------------------------------------------------------