My base problem is that I have a spreadsheet with 10's of thousands of FQDN (fully qualified domain name) entries that I need to check if the FQDN is a valid DNS entry on the public internet. I am doing a DNS lookup of each FQDN and would like to specify a public DNS server. If the call to the DNS returns an IP address, I will assume the FQDN is valid. I am working in excel 64-bit, but need a solution that will also compile and work in 32-bit, so I want the same source code to be able to be compiled in both. Since there are so many rows in the spreadsheet, I don't want to use a function that creates a temporary file for each lookup. (I am OCD about unneeded temporary files when a system call is available).
I believe that the function "getaddrinfoex" provides the ability to specify what name server is queried, but I have not been able to find any VBA snippets that use getaddrinfoex or the lesser version of getaddrinfo (which does not allow specifying the DNS server). I have found several examples of calls to gethostbyname, but all are for 32-bit Excel. Also, Microsoft has published that gethostbyname has been deprecated (https://msdn.microsoft.com/en-us/library/windows/desktop/ms738524(v=vs.85).aspx), so I was trying to use the recommended replacement getaddrinfo
How can I make a network connection with Visual Basic from Microsoft Access?
The snippet posted in the answer by #david in the question I linked above looks to have the proper syntax to be both 32-bit and 64-bit compatible. But the example did not include the call to gethostbyname, it only provided the declaration of the function.
Is getaddrinfoex available in VBA? Does someone have an example of using getaddrinfoex which will work in both 32-bit and 64-bit?
I would appreciate any help. I have not coded in MANY years, so my skills are very dated. Thus I am doing a lot of searches to find what I need.
Here is the code I have created from combining various searches on-line.
Private Type HOSTENT
hName As LongPtr
hAliases As LongPtr
hAddrType As Integer
hLen As Integer
hAddrList As LongPtr
End Type
#if Not VBA7 then
' used by 32-bit compiler
Private Declare Function gethostbyname Lib "wsock32.dll" _
(ByVal HostName As String) As LongPtr
Private Declare Function getaddrinfo Lib "wsock32.dll" _
(ByVal HostName As String) As LongPtr
Public Declare Function WSAStartup Lib "wsock32.dll" _
(ByVal wVersionRequired As Long, lpWSADATA As WSADATA) As LongPtr
#else
' used by 64-bit compiler
Private Declare PtrSafe Function gethostbyname Lib "wsock32.dll" _
(ByVal HostName As String) As LongPtr
Private Declare PtrSafe Function getaddrinfo Lib "wsock32.dll" _
(ByVal HostName As String) As LongPtr
Public Declare PtrSafe Function WSAStartup Lib "wsock32.dll" _
(ByVal wVersionRequired As Long, lpWSADATA As WSADATA) As LongPtr
#endif
Public Function GetIPAddressFromHostName(ByVal HostName As String) _
As LongPtr
Dim HostEntry As HOSTENT
Dim HostEntry2 as HOSTENT
Dim HostEntryPtr As LongPtr
Dim HostEntryPtr2 As LongPtr
Dim IPAddressesPtr As LongPtr
Dim Result As Long
If InitializeSockets Then
' I added the call do getaddrinfo as an example
' I have been able to get it to work at all
HostEntryPtr2 = getaddrinfo(HostName & vbNullChar)
HostEntryPtr = gethostbyname(HostName & vbNullChar)
If HostEntryPtr > 0 Then
CopyMemory HostEntry, ByVal HostEntryPtr, Len(HostEntryPtr)
CopyMemory IPAddressesPtr, ByVal HostEntry.hAddrList, _
Len(IPAddressesPtr)
CopyMemory Result, ByVal IPAddressesPtr, Len(Result)
GetIPAddressFromHostName = Result
End If
End If
End Function
Public Function InitializeSockets() As Boolean
' Initialize Windows sockets.
Dim WinSockData As WSADATA
InitializeSockets = WSAStartup(WS_VERSION_REQD, WinSockData) = 0
End Function
I have it working now as long as it is not moved to an add-in (.xlam). If I move it to an add-in this exact same code crashes on the call to getaddrinfo. I will continue to work on that.
The procedure requires one argument (hostname passed as a string). The second argument is the maximum number of IP addresses to return (passed as an integer), but is optional. If the second argument is blank, all IP address are returned. When set to a value other than zero, that value will be the maximum number of ip addresses for the host.
Private Const AF_UNSPEC As Long = 0
Private Const AF_INET As Long = 2
Private Const AF_INET6 As Long = 23
Private Const SOCK_STREAM As Long = 1
Private Const INADDR_ANY As Long = 0
Private Const IPPROTO_TCP As Long = 6
' Getaddrinfo return status codes
Private Const WAS_NOT_ENOUGH_MEMORY = 8 ' Insufficient memory available.
Private Const WASEINVAL = 10022 ' Invalid argument.
Private Const WASESOCKTNOSUPPORT = 10044 ' Socket type not supported.
Private Const WASEAFNOSUPPORT = 10047 ' Address family not supported by protocol family.
Private Const WASNOTINITIALISED = 10093 ' Successful WSAStartup not yet performed.
Private Const WASTYPE_NOT_FOUND = 10109 ' Class type not found.
Private Const WASHOST_NOT_FOUND = 11001 ' Host not found.
Private Const WASTRY_AGAIN = 11002 ' Nonauthoritative host not found.
Private Const WASNO_RECOVERY = 11003 ' This is a nonrecoverable error.
Private Const WASNO_DATA = 11004 ' Valid name, no data record of requested type.
'AI_flags
Private Const AI_PASSIVE As Long = &H1
Private Const ai_canonName As Long = &H2
Private Const AI_NUMERICHOST As Long = &H4
Private Const AI_ALL As Long = &H100
Private Const AI_ADDRCONFIG As Long = &H400
Private Const AI_V4MAPPED As Long = &H800
Private Const AI_NON_AUTHORITATIVE As Long = &H4000
Private Const AI_SECURE As Integer = &H8000
Private Const AI_RETURN_PREFERRED_NAMES As Long = &H10000
Private Const AI_FQDN As Long = &H20000
Private Const AI_FILESERVER As Long = &H40000
Dim hSocket As Long
Dim sServer As String
' To initialize Winsock.
Private Type WSADATA
wVersion As Integer
wHighVersion As Integer
szDescription(256 + 1) As Byte
szSystemstatus(128 + 1) As Byte
iMaxSockets As Integer
iMaxUpdDg As Integer
lpVendorInfo As Long
End Type
Private Type in_addr
s_addr As LongPtr
End Type
Private Type sockaddr_in
sin_family As Integer '2 bytes
sin_port As Integer '2 bytes
sin_addr As in_addr '4 bytes or 8 bytes
sin_zero(7) As Byte '8 bytes
End Type 'Total 16 bytes or 24 bytes
Private Type sockaddr
sa_family As Integer '2 bytes
sa_data(25) As Byte '26 bytes
End Type 'Total 28 bytes
Private Type addrinfo
ai_flags As Long
ai_family As Long
ai_socktype As Long
ai_protocol As Long
ai_addrlen As Long
ai_canonName As LongPtr 'strptr
ai_addr As LongPtr 'p sockaddr
ai_next As LongPtr 'p addrinfo
End Type
Private Declare PtrSafe Function API_Socket Lib "ws2_32.dll" Alias "socket" (ByVal af As Long, ByVal stype As Long, ByVal Protocol As Long) As Long
Private Declare PtrSafe Function API_GetAddrInfo Lib "ws2_32.dll" Alias "getaddrinfo" (ByVal NodeName As String, ByVal ServName As String, ByVal lpHints As LongPtr, lpResult As LongPtr) As Long
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare PtrSafe Function ntohs Lib "ws2_32.dll" (ByVal netshort As Long) As Integer
Public Function NameToIPaddress(hostname As String, Optional MaxReturn As Integer = 0) As String
Dim sa_local As sockaddr_in
Dim sa_dest As sockaddr
Dim lRet As Long
Dim Hints As addrinfo
Dim ptrResult As LongPtr
Dim IPaddress As String
Dim AddressList As String
Dim AddressType As Long
Dim Cnt As Integer
AddressType = AF_INET
If hostname = "" Then
NameToIPaddress = ""
Exit Function
End If
'Create TCP socket
hSocket = API_Socket(AddressType, SOCK_STREAM, IPPROTO_TCP)
If hSocket = 0 Then
MsgBox ("Failed to create socket!")
Exit Function
End If
'Populate the local sockaddr
sa_local.sin_family = AddressType
sa_local.sin_port = ntohs(0&)
sa_local.sin_addr.s_addr = INADDR_ANY
'Recover info about the destination.
'Hints.ai_flags = AI_NON_AUTHORITATIVE
Hints.ai_flags = 0
Hints.ai_family = AddressType
sServer = hostname & vbNullChar 'Null terminated string
sServer = hostname
lRet = API_GetAddrInfo(sServer, 0, VarPtr(Hints), ptrResult)
If lRet <> 0 Then
If lRet = WASHOST_NOT_FOUND Then
NameToIPaddress = "not found"
Exit Function
End If
Dim errorText As String
Select Case lRet
Case WAS_NOT_ENOUGH_MEMORY
errorText = "Insufficient memory available"
Case WASEINVAL
errorText = "Invalid argument"
Case WASESOCKTNOSUPPORT
errorText = "Socket type not supported"
Case WASEAFNOSUPPOR
errorText = "Address family not supported by protocol family"
Case WASNOTINITIALISED
errorText = "Successful WSAStartup not yet performed"
Case WASTYPE_NOT_FOUND
errorText = "Class type not found"
Case WASHOST_NOT_FOUND
errorText = "Host not found"
Case WASTRY_AGAIN
errorText = "Nonauthoritative host not found"
Case WASNO_RECOVERY
errorText = "This is a nonrecoverable error"
Case WASNO_DATA
errorText = "Valid name, no data record of requested type"
Case Else
errorText = "unknown error condition"
End Select
'MsgBox ("Error in GetAddrInfo: " & lRet & " - " & errorText)
NameToIPaddress = "#Error in lookup"
Exit Function
End If
Cnt = 0
Hints.ai_next = ptrResult 'Pointer to first structure in linked list
Do While Hints.ai_next > 0 And (Cnt < MaxReturn Or MaxReturn = 0)
CopyMemory Hints, ByVal Hints.ai_next, LenB(Hints) 'Copy next address info to Hints
CopyMemory sa_dest, ByVal Hints.ai_addr, LenB(sa_dest) 'Save sockaddr portion
Select Case sa_dest.sa_family
Case AF_INET
IPaddress = sa_dest.sa_data(2) & "." & sa_dest.sa_data(3) & "." & sa_dest.sa_data(4) & "." & sa_dest.sa_data(5)
Case AF_INET6
IPaddress = sa_dest.sa_data(0) & ":" & sa_dest.sa_data(1) & ":" & sa_dest.sa_data(2) & "::" & sa_dest.sa_data(3) & ":" & sa_dest.sa_data(4)
Case Else
IPaddress = ""
End Select
Cnt = Cnt + 1
If AddressList = "" Then
AddressList = IPaddress
Else
AddressList = AddressList & "," & IPaddress
End If
Loop
NameToIPaddress = AddressList
End Function
After weeks of research on this topic I've finally decided to start a thread of my own in hope there is someone out there with experience that can help. I've scoured the internet trying to understand the various coding examples out there, but I've come up short trying to put a working solution together. Let me start with some background--
Background:
I have a vb.net application that is getting delivered to Windows Servers and PCs in my organization using CA IT Client Manager (ITCM). Much like Microsoft SCCM, CA ITCM has an agent service running as SYSTEM on each PC. Hence, when my application gets delivered and executed on the target PC, it's running in the "NT Authority\SYSTEM" context.
The Problem:
During the initial phase of my application, there's a process running in the context of each logged in user that I need to stop. At the end of my applications execution, I have a requirement of restarting this process for each logged on user to prevent them from having to log off and back on again. The process I'm stopping is actually a system tray process that the user can interact with on their desktop.
Chasing a VB.NET Solution:
Researching endlessly on the internet, it seems there is no native .NET solution for this problem without having the password for each logged on user or prompting the user to enter some credentials. Since this is not an option for me, I need to find a way to start a process without having to know or require the logged on users credentials.
Researching this avenue led me to the CreateProcessAsUser Windows API function. From what I understand, I can do something along these lines -- (see below)
Notes:
This is my first time using unmanaged code calls in VB.NET to Windows APIs. There's a lot of ambiguity around the constants, enumerations and function declarations as I pieced the code together from various postings. Please do let me know if you notice any errors in any of these declarations. I have many questions about when a datatype needs to be "marshaled" as a different type. Please read carefully!!
Since there were countless examples from similar postings, I tried to follow the MSDN example from the CreateProcessAsUser page:
MSDN Link:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682429(v=vs.85).aspx
Example Link:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379608(v=vs.85).aspx
In addition to reviewing the validity of each Windows API call, please review the overall order of operations and let me know if I'm over-complicating this or even possibly missing something. The only code I have not implemented from the Microsoft example is allowing each SID full access to the interactive windows station, followed by allowing the SID full access to the interactive desktop. Maybe I'm wrong, but I figure each user should already have access to their interactive desktop already!
Public Class WindowsAPI
Private Const SE_CREATE_TOKEN_NAME As String = "SeCreateTokenPrivilege"
Private Const SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege"
Private Const SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege"
Private Const SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"
Private Const SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege"
Private Const SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege"
Private Const SE_TCB_NAME = "SeTcbPrivilege"
Private Const SE_SECURITY_NAME = "SeSecurityPrivilege"
Private Const SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"
Private Const SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege"
Private Const SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege"
Private Const SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege"
Private Const SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege"
Private Const SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege"
Private Const SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege"
Private Const SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege"
Private Const SE_BACKUP_NAME = "SeBackupPrivilege"
Private Const SE_RESTORE_NAME = "SeRestorePrivilege"
Private Const SE_SHUTDOWN_NAME = "SeShutdownPrivilege"
Private Const SE_DEBUG_NAME = "SeDebugPrivilege"
Private Const SE_AUDIT_NAME = "SeAuditPrivilege"
Private Const SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"
Private Const SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"
Private Const SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege"
Private Const SE_UNDOCK_NAME = "SeUndockPrivilege"
Private Const SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege"
Private Const SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege"
Private Const SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege"
Private Const SE_IMPERSONATE_NAME = "SeImpersonatePrivilege"
Private Const SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege"
Private Const SE_PRIVILEGE_ENABLED As Integer = &H2
Private Enum WindowShowStyle As UInteger
Hide = 0
ShowNormal = 1
ShowMinimized = 2
ShowMaximized = 3
Maximize = 3
ShowNormalNoActivate = 4
Show = 5
Minimize = 6
ShowMinNoActivate = 7
ShowNoActivate = 8
Restore = 9
ShowDefault = 10
ForceMinimized = 11
End Enum
Private Enum STARTF As Integer
STARTF_USESHOWWINDOW = &H1
STARTF_USESIZE = &H2
STARTF_USEPOSITION = &H4
STARTF_USECOUNTCHARS = &H8
STARTF_USEFILLATTRIBUTE = &H10
STARTF_RUNFULLSCREEN = &H20
STARTF_FORCEONFEEDBACK = &H40
STARTF_FORCEOFFFEEDBACK = &H80
STARTF_USESTDHANDLES = &H100
STARTF_USEHOTKEY = &H200
End Enum
Private Enum CreateProcessFlags
DEBUG_PROCESS = &H1
DEBUG_ONLY_THIS_PROCESS = &H2
CREATE_SUSPENDED = &H4
DETACHED_PROCESS = &H8
CREATE_NEW_CONSOLE = &H10
NORMAL_PRIORITY_CLASS = &H20
IDLE_PRIORITY_CLASS = &H40
HIGH_PRIORITY_CLASS = &H80
REALTIME_PRIORITY_CLASS = &H100
CREATE_NEW_PROCESS_GROUP = &H200
CREATE_UNICODE_ENVIRONMENT = &H400
CREATE_SEPARATE_WOW_VDM = &H800
CREATE_SHARED_WOW_VDM = &H1000
CREATE_FORCEDOS = &H2000
BELOW_NORMAL_PRIORITY_CLASS = &H4000
ABOVE_NORMAL_PRIORITY_CLASS = &H8000
INHERIT_PARENT_AFFINITY = &H10000
INHERIT_CALLER_PRIORITY = &H20000
CREATE_PROTECTED_PROCESS = &H40000
EXTENDED_STARTUPINFO_PRESENT = &H80000
PROCESS_MODE_BACKGROUND_BEGIN = &H100000
PROCESS_MODE_BACKGROUND_END = &H200000
CREATE_BREAKAWAY_FROM_JOB = &H1000000
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = &H2000000
CREATE_DEFAULT_ERROR_MODE = &H4000000
CREATE_NO_WINDOW = &H8000000
PROFILE_USER = &H10000000
PROFILE_KERNEL = &H20000000
PROFILE_SERVER = &H40000000
CREATE_IGNORE_SYSTEM_DEFAULT = &H80000000
End Enum
Private Enum ACCESS_MASK
DELETE = &H10000
READ_CONTROL = &H20000
WRITE_DAC = &H40000
WRITE_OWNER = &H80000
SYNCHRONIZE = &H100000
STANDARD_RIGHTS_REQUIRED = &HF0000
STANDARD_RIGHTS_READ = &H20000
STANDARD_RIGHTS_WRITE = &H20000
STANDARD_RIGHTS_EXECUTE = &H20000
STANDARD_RIGHTS_ALL = &H1F0000
SPECIFIC_RIGHTS_ALL = &HFFFF
ACCESS_SYSTEM_SECURITY = &H1000000
MAXIMUM_ALLOWED = &H2000000
GENERIC_READ = &H80000000
GENERIC_WRITE = &H40000000
GENERIC_EXECUTE = &H20000000
GENERIC_ALL = &H10000000
DESKTOP_READOBJECTS = &H1
DESKTOP_CREATEWINDOW = &H2
DESKTOP_CREATEMENU = &H4
DESKTOP_HOOKCONTROL = &H8
DESKTOP_JOURNALRECORD = &H10
DESKTOP_JOURNALPLAYBACK = &H20
DESKTOP_ENUMERATE = &H40
DESKTOP_WRITEOBJECTS = &H80
DESKTOP_SWITCHDESKTOP = &H100
WINSTA_ENUMDESKTOPS = &H1
WINSTA_READATTRIBUTES = &H2
WINSTA_ACCESSCLIPBOARD = &H4
WINSTA_CREATEDESKTOP = &H8
WINSTA_WRITEATTRIBUTES = &H10
WINSTA_ACCESSGLOBALATOMS = &H20
WINSTA_EXITWINDOWS = &H40
WINSTA_ENUMERATE = &H100
WINSTA_READSCREEN = &H200
WINSTA_ALL_ACCESS = &H37F
End Enum
<StructLayout(LayoutKind.Sequential)>
Private Structure PROCESS_INFORMATION
Public hProcess As IntPtr
Public hThread As IntPtr
Public dwProcessId As System.UInt32
Public dwThreadId As System.UInt32
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure SECURITY_ATTRIBUTES
Public nLength As System.UInt32
Public lpSecurityDescriptor As IntPtr
Public bInheritHandle As Boolean
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure STARTUPINFO
Public cb As System.UInt32
Public lpReserved As String
Public lpDesktop As String
Public lpTitle As String
Public dwX As System.UInt32
Public dwY As System.UInt32
Public dwXSize As System.UInt32
Public dwYSize As System.UInt32
Public dwXCountChars As System.UInt32
Public dwYCountChars As System.UInt32
Public dwFillAttribute As System.UInt32
Public dwFlags As System.UInt32
Public wShowWindow As Short
Public cbReserved2 As Short
Public lpReserved2 As IntPtr
Public hStdInput As IntPtr
Public hStdOutput As IntPtr
Public hStdError As IntPtr
End Structure
Private Enum SECURITY_IMPERSONATION_LEVEL
SecurityAnonymous = 0
SecurityIdentification = 1
SecurityImpersonation = 2
SecurityDelegation = 3
End Enum
Private Enum TOKEN_TYPE
TokenPrimary = 1
TokenImpersonation = 2
End Enum
Structure LUID
Public LowPart As UInt32
Public HighPart As Integer
End Structure
Structure TOKEN_PRIVILEGES
Public PrivilegeCount As Integer
Public TheLuid As LUID
Public Attributes As Integer
End Structure
Enum TOKEN_INFORMATION_CLASS
TokenUser = 1
TokenGroups
TokenPrivileges
TokenOwner
TokenPrimaryGroup
TokenDefaultDacl
TokenSource
TokenType
TokenImpersonationLevel
TokenStatistics
TokenRestrictedSids
TokenSessionId
TokenGroupsAndPrivileges
TokenSessionReference
TokenSandBoxInert
TokenAuditPolicy
TokenOrigin
TokenElevationType
TokenLinkedToken
TokenElevation
TokenHasRestrictions
TokenAccessInformation
TokenVirtualizationAllowed
TokenVirtualizationEnabled
TokenIntegrityLevel
TokenUIAccess
TokenMandatoryPolicy
TokenLogonSid
MaxTokenInfoClass
End Enum
<StructLayoutAttribute(LayoutKind.Sequential)>
Public Structure SECURITY_DESCRIPTOR
Public revision As Byte
Public size As Byte
Public control As Short
Public owner As IntPtr
Public group As IntPtr
Public sacl As IntPtr
Public dacl As IntPtr
End Structure
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function AdjustTokenPrivileges(ByVal TokenHandle As IntPtr,
ByVal DisableAllPrivileges As Boolean,
ByRef NewState As TOKEN_PRIVILEGES,
ByVal BufferLengthInBytes As UInt32,
ByRef PreviousState As TOKEN_PRIVILEGES,
ByRef ReturnLengthInBytes As UInt32) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CreateProcessAsUser(ByVal hToken As IntPtr,
ByVal lpApplicationName As String,
ByVal lpCommandLine As String,
ByRef lpProcessAttributes As SECURITY_ATTRIBUTES,
ByRef lpThreadAttributes As SECURITY_ATTRIBUTES,
ByVal bInheritHandles As Boolean,
ByVal dwCreationFlags As UInteger,
ByVal lpEnvironment As IntPtr,
ByVal lpCurrentDirectory As String,
ByRef lpStartupInfo As STARTUPINFO,
ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateTokenEx(ByVal hExistingToken As IntPtr,
ByVal dwDesiredAccess As UInteger,
ByRef lpTokenAttributes As SECURITY_ATTRIBUTES,
ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
ByVal TokenType As TOKEN_TYPE,
ByRef phNewToken As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function ImpersonateLoggedOnUser(ByVal hToken As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenProcessToken(ByVal ProcessHandle As IntPtr,
ByVal DesiredAccess As Integer,
ByRef TokenHandle As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function RevertToSelf() As Boolean
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CloseHandle(ByVal hObject As IntPtr) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function GetProcessWindowStation() As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenDesktop(ByVal lpszDesktop As String,
ByVal dwFlags As Integer,
ByVal fInderit As Boolean,
ByVal dwDesiredAccess As Integer) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenWindowStation(ByVal lpszWinSta As String,
ByVal fInherit As Boolean,
ByVal dwDesiredAccess As ACCESS_MASK) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function SetProcessWindowStation(ByVal hWinSta As IntPtr) As Boolean
End Function
Public Shared Function LaunchProcess(ByVal CmdLine As String) As Boolean
' Declare and initialize variables
Dim ExplorerProcesses As Process()
Dim UserTokenHandle As IntPtr
Dim PrimaryTokenHandle As IntPtr
Dim CurrentWinStationHandle As IntPtr
Dim InteractiveWinStationHandle As IntPtr
Dim InteractiveDesktopHandle As IntPtr
Dim StartupInfo As STARTUPINFO
Dim ProcessInfo As PROCESS_INFORMATION
' Get all explorer.exe IDs
ExplorerProcesses = Process.GetProcessesByName("explorer")
' Verify explorers were found
If ExplorerProcesses.Length = 0 Then
' Return
Return True
End If
' Iterate each explorer.exe process
For Each ExplorerProcess As Process In ExplorerProcesses
' Get the user token handle address (Query access level)
If OpenProcessToken(ExplorerProcess.Handle, TokenAccessLevels.MaximumAllowed, UserTokenHandle) = False Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Get a primary token
If DuplicateTokenEx(UserTokenHandle,
TokenAccessLevels.MaximumAllowed,
Nothing,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
PrimaryTokenHandle) = False Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Save a handle to the current window station
CurrentWinStationHandle = GetProcessWindowStation()
' Check for valid handle to the windows station
If CurrentWinStationHandle = IntPtr.Zero Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Get a handle to the interactive window station
InteractiveWinStationHandle = OpenWindowStation("winsta0", False, ACCESS_MASK.READ_CONTROL Or ACCESS_MASK.WRITE_DAC)
' Check for a valid handle
If InteractiveWinStationHandle = Nothing Then
' Do some error handling
' Iterate the next user
Continue For
End If
' To get the correct default desktop, set the caller's window station to the interactive window station
If SetProcessWindowStation(InteractiveWinStationHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Get handle to interactive desktop
InteractiveDesktopHandle = OpenDesktop("default",
0,
False,
ACCESS_MASK.READ_CONTROL Or
ACCESS_MASK.WRITE_DAC Or
ACCESS_MASK.DESKTOP_WRITEOBJECTS Or
ACCESS_MASK.DESKTOP_READOBJECTS)
' Restore the caller's window station
If SetProcessWindowStation(CurrentWinStationHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Check for a valid handle
If InteractiveDesktopHandle = IntPtr.Zero Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Initialize process and startup info
ProcessInfo = New PROCESS_INFORMATION
StartupInfo = New STARTUPINFO
StartupInfo.cb = Marshal.SizeOf(StartupInfo)
StartupInfo.lpDesktop = "winsta0\default"
' Impersonate client to ensure access to executable file
If ImpersonateLoggedOnUser(PrimaryTokenHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Launch the process in the client's logon session
If CreateProcessAsUser(PrimaryTokenHandle,
Nothing,
CmdLine,
Nothing,
Nothing,
False,
CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT Or
CreateProcessFlags.NORMAL_PRIORITY_CLASS Or,
Nothing,
Nothing,
StartupInfo,
ProcessInfo) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' End impersonation of client
If RevertToSelf() = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
Next
' Check for open handle
If Not PrimaryTokenHandle = IntPtr.Zero Then
' Close the handle
CloseHandle(PrimaryTokenHandle)
End If
' Return
Return True
End Function
End Class
My Result:
Currently, I'm testing calls to the LaunchProcess function from my Administrator account (running my solution from Visual Studio 2010 on my local machine) and by delivering the application through ITCM (running from the SYSTEM account on my local machine). In both cases I get the specified process to start in the users session, but with the following error:
"The application was unable to start correctly (0xc0000142). Click OK to close the application."
I'm hoping someone can review my code and point me in the right direction. Otherwise, please advise on how I can possibly debug what the heck is going wrong here.
Many thanks for all your input in advance.
The Solution:
Before posting the full code solution, I wanted to share how I found my answer. After revisiting the MSDN article on the CreateProcessAsUser API function, I realized I needed to verify if my process actually held the required privileges mentioned by the article:
SE_INCREASE_QUOTA_NAME
SE_ASSIGNPRIMARYTOKEN_NAME
Also, not mentioned in the article, but perhaps critical to some of the other related Windows API calls for looking up and adjusting token privileges to enable the above privileges is:
SE_TCB_NAME
Recall my application is getting delivered using CA's IT Client Manager (ITCM) software to target Windows Servers and PCs. The core CA ITCM agent service logs on as "Local System", but its plugins that perform all the dirty work startup and run as the SYSTEM account. Apparently there is a HUGE difference between the SYSTEM account and the "Local System" account.
Using SysInternal's Process Explorer tool, I was able to inspect my application and find that it did not hold all of the required privileges. It's been a while since I ran the test, so I forget which privilege was actually not held.
The initial mistake I made was writing unmanaged Windows API function calls to try to enable the missing privilege, but unfortunately it doesn't work that way. Either your process has the privilege or it doesn't, period. If it holds the privilege, then your only responsibility it to make sure the privilege is enabled.
To overcome this, I had to use a different approach. In order to obtain the privileges I required, my application needed to be installed and executed as a service, so it could be running as the "Local System" account. However, to redesign the entire application as an installable service just to satisfy this one simple requirement didn't make sense.
Instead, I created a second VB.NET project, which is the code I'm posting. The second project is a simple Windows Service that takes startup parameters. The first startup parameter is the application you would like launched for each logged on user. Any remaining startup parameters are passed as startup switches to the application you specify :-)
Armed with a service that now held the proper privileges and can dynamically receive startup parameters for specifying exactly what you want launched for each logged in user, I embedded the resultant executable in my first project.
My first project, running as the SYSTEM account, HAS the rights/permissions/privileges to install a new system services. It also has the rights to start that system services, passing the parameters I needed to start the tray service for each logged in user in the system. Problem SOLVED!
Here's the code for my Windows Service--
LaunchService.vb:
'****************************** Class Header *******************************\
' Project Name: LaunchService
' Class Name: LaunchService
' File Name: LaunchService.vb
' Author: fonbr01
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
' IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
' OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
' ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
' OTHER DEALINGS IN THE SOFTWARE.
'***************************************************************************/
Public Class LaunchService
Protected Overrides Sub OnStart(ByVal args() As String)
' Local Variables
Dim AppName As String
' Get the application name
AppName = args(0)
args(0) = " "
' Check for additional arguments
If args.Length > 1 Then
' Shift the arguments
For i As Integer = 1 To args.Length - 1
' Swap the args
args(i - 1) = args(i)
Next
' Remove the last argument
args(args.Length - 1) = ""
End If
' Launch the App for all users
WindowsAPI.LaunchProcess(AppName, args)
End Sub
Protected Overrides Sub OnStop()
End Sub
End Class
WindowsAPI.vb:
'****************************** Class Header *******************************\
' Project Name: LaunchService
' Class Name: WindowsAPI
' File Name: WindowsAPI.vb
' Author: fonbr01
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
' IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
' OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
' ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
' OTHER DEALINGS IN THE SOFTWARE.
'***************************************************************************/
' Imports
Imports Microsoft.Win32.SafeHandles
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Diagnostics
' Windows API Class
Public Class WindowsAPI
' *************************
' * Windows API Functions
' *************************
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function AdjustTokenPrivileges(
<[In]()> ByVal TokenHandle As SafeTokenHandle,
<[In](), MarshalAs(UnmanagedType.Bool)> ByVal DisableAllPrivileges As Boolean,
<[In]()> ByRef NewState As TOKEN_PRIVILEGES,
<[In]()> ByVal BufferLengthInBytes As UInt32,
<Out()> ByRef PreviousState As TOKEN_PRIVILEGES,
<Out()> ByRef ReturnLengthInBytes As UInt32) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CreateProcessAsUser(
<[In]()> ByVal hToken As SafeTokenHandle,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpApplicationName As String,
<[In](), Out(), MarshalAs(UnmanagedType.LPWStr)> ByVal lpCommandLine As String,
<[In]()> ByRef lpProcessAttributes As SECURITY_ATTRIBUTES,
<[In]()> ByRef lpThreadAttributes As SECURITY_ATTRIBUTES,
<[In](), MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandles As Boolean,
<[In]()> ByVal dwCreationFlags As UInteger,
<[In]()> ByVal lpEnvironment As IntPtr,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpCurrentDirectory As String,
<[In]()> ByRef lpStartupInfo As STARTUPINFO,
<Out()> ByRef lpProcessInformation As PROCESS_INFORMATION) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateToken(
<[In]()> ByVal ExistingTokenHandle As SafeTokenHandle,
<[In]()> ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
<Out()> ByRef DuplicateTokenHandle As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateTokenEx(
<[In]()> ByVal hExistingToken As IntPtr,
<[In]()> ByVal dwDesiredAccess As UInteger,
<[In]()> ByRef lpTokenAttributes As SECURITY_ATTRIBUTES,
<[In]()> ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
<[In]()> ByVal TokenType As TOKEN_TYPE,
<Out()> ByRef phNewToken As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function LookupPrivilegeValue(
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpSystemName As String,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpName As String,
<Out()> ByRef lpLuid As LUID) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function OpenProcessToken(
<[In]()> ByVal hProcess As IntPtr,
<[In]()> ByVal desiredAccess As UInt32,
<Out()> ByRef hToken As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
' *************************
' * Structures
' *************************
<StructLayout(LayoutKind.Sequential)>
Private Structure PROCESS_INFORMATION
Public hProcess As IntPtr
Public hThread As IntPtr
Public dwProcessId As System.UInt32
Public dwThreadId As System.UInt32
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure SECURITY_ATTRIBUTES
Public nLength As System.UInt32
Public lpSecurityDescriptor As IntPtr
Public bInheritHandle As Boolean
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure STARTUPINFO
Public cb As System.UInt32
Public lpReserved As String
Public lpDesktop As String
Public lpTitle As String
Public dwX As System.UInt32
Public dwY As System.UInt32
Public dwXSize As System.UInt32
Public dwYSize As System.UInt32
Public dwXCountChars As System.UInt32
Public dwYCountChars As System.UInt32
Public dwFillAttribute As System.UInt32
Public dwFlags As System.UInt32
Public wShowWindow As Short
Public cbReserved2 As Short
Public lpReserved2 As IntPtr
Public hStdInput As IntPtr
Public hStdOutput As IntPtr
Public hStdError As IntPtr
End Structure
Private Structure LUID
Public LowPart As UInt32
Public HighPart As Integer
End Structure
Private Structure LUID_AND_ATTRIBUTES
Public Luid As LUID
Public Attributes As Integer
End Structure
Private Structure TOKEN_PRIVILEGES
Public PrivilegeCount As UInt32
<MarshalAs(UnmanagedType.ByValArray)> Public Privileges() As LUID_AND_ATTRIBUTES
End Structure
' ******************************
' * Enumerations
' ******************************
Private Enum CreateProcessFlags
DEBUG_PROCESS = &H1
DEBUG_ONLY_THIS_PROCESS = &H2
CREATE_SUSPENDED = &H4
DETACHED_PROCESS = &H8
CREATE_NEW_CONSOLE = &H10
NORMAL_PRIORITY_CLASS = &H20
IDLE_PRIORITY_CLASS = &H40
HIGH_PRIORITY_CLASS = &H80
REALTIME_PRIORITY_CLASS = &H100
CREATE_NEW_PROCESS_GROUP = &H200
CREATE_UNICODE_ENVIRONMENT = &H400
CREATE_SEPARATE_WOW_VDM = &H800
CREATE_SHARED_WOW_VDM = &H1000
CREATE_FORCEDOS = &H2000
BELOW_NORMAL_PRIORITY_CLASS = &H4000
ABOVE_NORMAL_PRIORITY_CLASS = &H8000
INHERIT_PARENT_AFFINITY = &H10000
INHERIT_CALLER_PRIORITY = &H20000
CREATE_PROTECTED_PROCESS = &H40000
EXTENDED_STARTUPINFO_PRESENT = &H80000
PROCESS_MODE_BACKGROUND_BEGIN = &H100000
PROCESS_MODE_BACKGROUND_END = &H200000
CREATE_BREAKAWAY_FROM_JOB = &H1000000
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = &H2000000
CREATE_DEFAULT_ERROR_MODE = &H4000000
CREATE_NO_WINDOW = &H8000000
PROFILE_USER = &H10000000
PROFILE_KERNEL = &H20000000
PROFILE_SERVER = &H40000000
CREATE_IGNORE_SYSTEM_DEFAULT = &H80000000
End Enum
Private Enum SECURITY_IMPERSONATION_LEVEL
SecurityAnonymous = 0
SecurityIdentification
SecurityImpersonation
SecurityDelegation
End Enum
Private Enum TOKEN_TYPE
TokenPrimary = 1
TokenImpersonation = 2
End Enum
' ******************************
' * Constants
' ******************************
Private Const SE_ASSIGNPRIMARYTOKEN_NAME As String = "SeAssignPrimaryTokenPrivilege"
Private Const SE_INCREASE_QUOTA_NAME As String = "SeIncreaseQuotaPrivilege"
Private Const SE_TCB_NAME As String = "SeTcbPrivilege"
Private Const SE_PRIVILEGE_ENABLED As UInt32 = &H2
' ******************************
' * Safe Token Handle Class
' ******************************
Private Class SafeTokenHandle
Inherits SafeHandleZeroOrMinusOneIsInvalid
Private Sub New()
MyBase.New(True)
End Sub
Friend Sub New(ByVal handle As IntPtr)
MyBase.New(True)
MyBase.SetHandle(handle)
End Sub
<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
End Function
Protected Overrides Function ReleaseHandle() As Boolean
Return SafeTokenHandle.CloseHandle(MyBase.handle)
End Function
End Class
' ******************************
' * Increase Privileges Function
' ******************************
Public Shared Function IncreasePrivileges() As Boolean
' Local variables
Dim hToken As SafeTokenHandle = Nothing
Dim luid As LUID
Dim NewState As TOKEN_PRIVILEGES
NewState.PrivilegeCount = 1
ReDim NewState.Privileges(0)
' Get current process token
If OpenProcessToken(Diagnostics.Process.GetCurrentProcess.Handle, TokenAccessLevels.MaximumAllowed, hToken) = False Then
' Write debug
WriteEvent("Error: Windows API OpenProcessToken function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeIncreaseQuotaPrivilege
If Not LookupPrivilegeValue(Nothing, SE_INCREASE_QUOTA_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeIncreaseQuotaPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeAssignPrimaryTokenPrivilege
If Not LookupPrivilegeValue(Nothing, SE_ASSIGNPRIMARYTOKEN_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeAssignPrimaryTokenPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeTcbPrivilege
If Not LookupPrivilegeValue(Nothing, SE_TCB_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeTcbPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Return
Return True
End Function
' ******************************
' * Launch Process Sub
' ******************************
Public Shared Sub LaunchProcess(ByVal CmdLine As String, ByVal args As String())
' Local variables
Dim Arguments As String = ""
Dim ExplorerProcesses As Process()
Dim hToken As SafeTokenHandle = Nothing
Dim principle As WindowsIdentity
Dim phNewToken As SafeTokenHandle = Nothing
Dim si As STARTUPINFO
Dim pi As PROCESS_INFORMATION
' Process arguments
For Each arg As String In args
' Build argument string
Arguments += " " + arg
Next
' Increase Privileges
If IncreasePrivileges() = False Then
' Write debug
WriteEvent("Warning: Failed to increase current process privileges.", EventLogEntryType.Warning)
End If
' Get all explorer.exe IDs
ExplorerProcesses = Process.GetProcessesByName("explorer")
' Verify explorers were found
If ExplorerProcesses.Length = 0 Then
' Write debug
WriteEvent("Warning: No explorer.exe processes found.", EventLogEntryType.Warning)
' Return
Exit Sub
End If
' Iterate each explorer.exe process
For Each hProcess As Process In ExplorerProcesses
' Get the user token handle
If OpenProcessToken(hProcess.Handle, TokenAccessLevels.MaximumAllowed, hToken) = False Then
' Write debug
WriteEvent("Error: Windows API OpenProcessToken function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Iterate the next process
Continue For
End If
' Get the windows identity
principle = New WindowsIdentity(hToken.DangerousGetHandle)
' Get a primary token
If Not DuplicateTokenEx(hToken.DangerousGetHandle,
TokenAccessLevels.MaximumAllowed,
Nothing,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
phNewToken) Then
' Write debug
WriteEvent("Error: Windows API DuplicateTokenEx function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Iterate the next process
Continue For
End If
' Initialize process and startup info
pi = New PROCESS_INFORMATION
si = New STARTUPINFO
si.cb = Marshal.SizeOf(si)
si.lpDesktop = Nothing
' Launch the process in the client's logon session
If Not CreateProcessAsUser(phNewToken,
Nothing,
CmdLine + Arguments,
Nothing,
Nothing,
False,
CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT,
Nothing,
Nothing,
si,
pi) Then
' Write debug
WriteEvent("Error: Windows API CreateProcessAsUser function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
Else
' Write debug
WriteEvent("Created new user process: " + Environment.NewLine +
"User: " + principle.Name + Environment.NewLine +
"Process: " + CmdLine + Arguments + Environment.NewLine +
"PID: " + pi.dwProcessId.ToString, EventLogEntryType.Information)
End If
' Free resources
hToken.Close()
hToken = Nothing
phNewToken.Close()
phNewToken = Nothing
principle = Nothing
pi = Nothing
si = Nothing
Next
End Sub
' ******************************
' * Write Event Log Sub
' ******************************
Public Shared Sub WriteEvent(EventMessage As String, EntryType As EventLogEntryType)
' Check if event source exists
If Not EventLog.SourceExists("WinOffline Launch Service") Then
' Create the event source
EventLog.CreateEventSource("WinOffline Launch Service", "System")
End If
' Write the message
EventLog.WriteEntry("WinOffline Launch Service", EventMessage, EntryType)
End Sub
End Class
Implementation:
As mentioned above, I've taken the service executable from my second project, added it in Visual Studio as an "existing item" to my first project. I then changed the "build action" to embed the service executable into my first applications executable.
When the first application executes on the target, it runs code to extract the embedded executable to the target machine. You can Google for that code, it's rather simplistic.
After the embedded service executable is extracted to a path on the local system, here's the four "sc" commands I run--
sc create <ServiceName> binpath= <Full Path to Service Executable> start= demand
sc start <ServiceName> <Full Path to App to Launch for all Users> <Parameters>
sc stop <ServiceName>
sc delete <ServiceName>
Note: In the first sc command remember to put spaces after the equal signs.
Using sc commands is far simpler than using installutil.exe or creating a setup project to package the service in an MSI and install it. Just make sure you WAIT for each sc command to return before proceeding to the next one.
Final Note:
To all those that provided positive insight and feedback, thank you very much for all your help. It's for good people like yourselves that I gladly post my code. For all those who told me this couldn't be done or tried to assert it's somehow malicious to poke into users desktops and to run something, I politely encourage you to start thinking out of the box. I wouldn't want you working on my team!
It's a slippery slope to walk through life with the mentality that just because something could have malicious or dangerous implications in the wrong hands that it shouldn't be accomplished at all. Your stinking thinking is contrary to all that's beautiful in the universe.
I am having trouble converting some code from VB6 to VB.NET (I don't have as much experience with .NET). When I run the 'Select function (from the WS2_32.dll library) in .NET, using the same parameters as the VB6 program, it returns a result of -1 (indicating an error). I think the error may be related to an upgrade comment I saw about marshalling, but I was not sure what I needed to do to declare the function differently. Here is the code that I believe is related to the problem (including the upgrade warnings from Visual Studios):
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure FD_SET
Dim fd_count As Integer
<VBFixedArray(FD_SETSIZE)> Dim fd_array() As Integer
Public Sub Initialize()
ReDim fd_array(FD_SETSIZE)
End Sub
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure TIMEVAL
Dim tv_sec As Integer
Dim tv_usec As Integer
End Structure
'UPGRADE_WARNING: Structure TIMEVAL may require marshalling attributes to be passed as an argument in this Declare statement.
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement.
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement.
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement.
Private Declare Function bsd_select Lib "WS2_32.dll" Alias "select" (ByVal nfds As Integer, ByRef readfds As FD_SET, ByRef writefds As FD_SET, ByRef exceptfds As FD_SET, ByRef timeout As TIMEVAL) As Integer
nResult = bsd_select(0, fdsRead, fdsWrite, fdsExcept, tvTimeout) 'the first parameter is ignored in Windows Sockets 2
Here is the code for the entire program. Thanks in advance!
Option Strict Off
Option Explicit On
Imports System.Runtime.InteropServices
Module modTCPCommunicaiton
'Constants used with Windows Sockets
Private Const AF_INET As Integer = 2
Private Const SOCK_STREAM As Integer = 1
Private Const IPPROTO_TCP As Integer = 6
Private Const FD_SETSIZE As Integer = 64
Private Const SOCKET_ERROR As Integer = -1
Private Const INVALID_SOCKET As Integer = -1
'Constants used for clarity
Private Const WS_VERSION_1_1 As Short = 257
Private Const SOCKADDR_LEN As Integer = 16
Private Const NO_FLAGS As Integer = 0
Private Const MAX_REPLY_LEN As Integer = 3200
'Define structures used with Windows Sockets
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure WSADATA
Dim wVersion As Short
Dim wHighVersion As Short
'UPGRADE_WARNING: Fixed-length string size must fit in the buffer. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="3C1E4426-0B80-443E-B943-0627CD55D48B"'
<VBFixedString(258), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=258)> Public szDescription() As Char
'UPGRADE_WARNING: Fixed-length string size must fit in the buffer. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="3C1E4426-0B80-443E-B943-0627CD55D48B"'
<VBFixedString(130), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=130)> Public szSystemStatus() As Char
Dim iMaxSockets As Short
Dim iMaxUdpDg As Short
Dim lpVenderInfo As String
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure SOCKADDR_IN
Dim sin_family As Short
Dim sin_port As Short
Dim sin_addr As Integer
<VBFixedArray(8)> Dim sin_zero() As Byte 'this is just padding to make the whole structure size to be 16 bytes.
Public Sub Initialize()
ReDim sin_zero(8)
End Sub
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure FD_SET
Dim fd_count As Integer
<VBFixedArray(FD_SETSIZE)> Dim fd_array() As Integer
Public Sub Initialize()
ReDim fd_array(FD_SETSIZE)
End Sub
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Private Structure TIMEVAL
Dim tv_sec As Integer
Dim tv_usec As Integer
End Structure
'Declare imported Windows Sockets functions
'UPGRADE_WARNING: Structure WSADATA may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
Private Declare Function WSAStartup Lib "WS2_32.dll" (ByVal wVersionRequested As Short, <[In](), Out()> ByRef lpWSAData As WSADATA) As Integer
Private Declare Function WSACleanup Lib "WS2_32.dll" () As Integer
Private Declare Function WSAGetLastError Lib "WS2_32.dll" () As Integer
Private Declare Function inet_addr Lib "WS2_32.dll" (ByVal szIPv4 As String) As Integer
Private Declare Function htons Lib "WS2_32.dll" (ByVal short_int As Short) As Short
Private Declare Function socket Lib "WS2_32.dll" (ByVal af As Integer, ByVal sock_type As Integer, ByVal protocol As Integer) As Integer
'UPGRADE_WARNING: Structure SOCKADDR_IN may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
Private Declare Function connect Lib "WS2_32.dll" (ByVal s As Integer, <[In](), Out()> ByRef name As SOCKADDR_IN, ByVal namelen As Integer) As Integer
Private Declare Function send Lib "WS2_32.dll" (ByVal s As Integer, ByVal buf As String, ByVal length As Integer, ByVal flags As Integer) As Integer
'Have to rename the "select" function due to conflict with reserved word in VB
'UPGRADE_WARNING: Structure TIMEVAL may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
'UPGRADE_WARNING: Structure FD_SET may require marshalling attributes to be passed as an argument in this Declare statement. Click for more: 'ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?keyword="C429C3A5-5D47-4CD9-8F51-74A1616405DC"'
Private Declare Function bsd_select Lib "WS2_32.dll" Alias "select" (ByVal nfds As Integer, <[In](), Out()> ByRef readfds As FD_SET, <[In](), Out()> ByRef writefds As FD_SET, <[In](), Out()> ByRef exceptfds As FD_SET, <[In]()> ByRef timeout As TIMEVAL) As Integer
Private Declare Function recv Lib "WS2_32.dll" (ByVal s As Integer, ByVal buf As String, ByVal length As Integer, ByVal flags As Integer) As Integer
Private Declare Function closesocket Lib "WS2_32.dll" (ByVal s As Integer) As Integer
'Variables used in this module
Dim hSock As Integer
Dim nResult As Integer
Dim fs As New Scripting.FileSystemObject 'Requires reference to "Microsoft Scripting Runtime" (system32\scrrun.dll)
Dim fileOut As Scripting.TextStream
Public Sub Main()
'This code provides a simple example of TCP communication to a SAFER DAS.
'After initializing and connecting to the DAS, several requests will be
'sent and the results logged to a text file. When finished, the notepad
'file will be opened. Users may modify this sample however they like.
'Initialize and attempt to connect to SAFER DAS
If Not SC_Init() Then
Exit Sub
End If
'We are now connected and ready to transact data with the DAS.
'The subroutine will take care of properly formatting the request string.
Call SC_SendRequest("SI 1")
'Call SC_SendRequest("SS 1")
'Call SC_SendRequest("SA 1,1")
'Call SC_SendRequest("SH 1,1")
'Call SC_SendRequest("SM 1,1")
'Call SC_SendRequest("SN 1,1")
'Call SC_SendRequest("SP 1,1")
'Call SC_SendRequest("SQ 1,1")
'We're finished, so close down the socket connection
Call SC_Close()
'Open the output text file
Call Shell("notepad.exe SAFER_com.txt", AppWinStyle.NormalFocus)
End Sub
Private Function SC_Init() As Boolean
SC_Init = False
'Prompt for IP address and port number to connect to
Dim sIPAddr As String
Dim sPort As String
sIPAddr = "151.163.221.93"
If sIPAddr = "" Then
Exit Function
End If
sPort = "3000"
If sPort = "" Then
Exit Function
End If
'Get the output file ready
On Error GoTo ErrorHandler
fileOut = fs.CreateTextFile("SAFER_com.txt", True)
'Initialize Windows Sockets 2
Dim wsAttrib As WSADATA
nResult = WSAStartup(WS_VERSION_1_1, wsAttrib)
If Not nResult = 0 Then
MsgBox("WSAStartup Error = " & nResult)
fileOut.Close()
Exit Function
End If
'Create a TCP socket
hSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
If hSock = INVALID_SOCKET Then
MsgBox("socket() Error = " & WSAGetLastError())
nResult = WSACleanup()
fileOut.Close()
Exit Function
End If
'Prepare the address data structure
Dim saAddress As New SOCKADDR_IN
saAddress.Initialize()
saAddress.sin_family = AF_INET
saAddress.sin_port = htons(Val(sPort))
saAddress.sin_addr = inet_addr(sIPAddr)
'Try connecting to the server
nResult = connect(hSock, saAddress, SOCKADDR_LEN)
If nResult = SOCKET_ERROR Then
MsgBox("connect() Error = " & WSAGetLastError())
nResult = WSACleanup()
fileOut.Close()
Exit Function
End If
'Successfully connected to the server
SC_Init = True
Exit Function
ErrorHandler:
MsgBox("Could not create output file.")
End Function
Private Sub SC_Close()
'Send special close request to DAS (5 bytes to send)
Dim sRequest As New VB6.FixedLengthString(15)
sRequest.Value = Chr(1) & Chr(0) & Chr(0) & Chr(0) & Chr(0)
nResult = send(hSock, sRequest.Value, 5, NO_FLAGS)
'Close our socket and shutdown Windows Sockets 2
nResult = closesocket(hSock)
nResult = WSACleanup()
fileOut.Close()
End Sub
Private Sub SC_SendRequest(ByRef sReq As String)
'Test for valid request length
If (Len(RTrim(sReq)) < 4) Or (Len(RTrim(sReq)) > 9) Then
MsgBox("Request is not valid")
Exit Sub
End If
'Format the request for sending to SAFER DAS by TCP port
Dim sRequest As New VB6.FixedLengthString(15)
sRequest.Value = Chr(0) & Chr(0) & Chr(0) & RTrim(sReq) & Chr(13) & Chr(0) & Chr(0)
'Send the formatted request
Dim nLen As Integer
nLen = 6 + Len(RTrim(sReq)) 'Include the 6 extra format bytes in the length
nResult = send(hSock, sRequest.Value, nLen, NO_FLAGS)
fileOut.WriteLine((sReq))
fileOut.WriteBlankLines((1))
'NOTE: When using the recv() function in blocking mode (default), it may wait forever
'for the other side to send some data. This can be problematic for your application.
'The select() function can be used to watch for the reply data to arrive with a timeout.
'If data has still not arrived after a generous timeout expires, it probably never will.
'In that case, the whole socket connection may be suspect and you might want to close it
'and start over.
'Prepare data structures for use with select() function
Dim fdsWrite, fdsRead, fdsExcept As New FD_SET
fdsWrite.Initialize()
fdsRead.Initialize()
fdsExcept.Initialize()
Dim tvTimeout As TIMEVAL
fdsRead.fd_count = 1 'how many sockets to check for incoming data
fdsRead.fd_array(0) = hSock 'which sockets to check
fdsWrite.fd_count = 0
fdsExcept.fd_count = 0
tvTimeout.tv_sec = 5 '5-second timeout
tvTimeout.tv_usec = 0
'Wait up to timeout for data to arrive from SAFER DAS
'System.Threading.Thread.Sleep(5000)
nResult = bsd_select(0, fdsRead, fdsWrite, fdsExcept, tvTimeout) 'the first parameter is ignored in Windows Sockets 2
Dim sReply As New VB6.FixedLengthString(MAX_REPLY_LEN)
Dim sData As String
If nResult = 1 Then
'select() reports that some data has arrived for 1 socket (our only socket)
nLen = recv(hSock, sReply.Value, MAX_REPLY_LEN, NO_FLAGS)
'nLen = recv(1001, sReply.Value, MAX_REPLY_LEN, NO_FLAGS)
'Get the length of just the SAFER data by excluding the first 3 wrapper bytes,
'the <LF> end delimiter, and the last 2 wrapper bytes.
nLen = nLen - 6
'Extract the SAFER data packet from the reply string, skipping over the first 3 wrapper bytes
sData = Mid(sReply.Value, 4, nLen)
fileOut.WriteLine((sData))
fileOut.WriteBlankLines((1))
ElseIf nResult = 0 Then
'select() timed out
MsgBox("Timed out waiting for reply" & nResult)
fileOut.WriteLine(("Timed out waiting for reply" & nResult))
fileOut.WriteBlankLines((1))
Else
'Some other error occurred
MsgBox("select() Error = " & WSAGetLastError())
fileOut.WriteLine(("select() Error = " & WSAGetLastError()))
fileOut.WriteBlankLines((1))
End If
End Sub
End Module
Try to set attribute MarshalAs(UnmanagedType.LPStruct)> on arguments you've received warnings about. It should help.
Also, IMHO it will be better if you rewrite your code using System.Net.Socket class ASAP. It will be much more short, simple and robust.
The problem in this case is the definition of the structure WSADATA; I used the WSAData64 structure reported here as I am under a 64 bit system and it worked without changing anything.
I am reporting the 32 bit and 64 bit structures here in case the link dies over time:
Private Structure WSAData32
Public Version As UShort
Public HighestVersion As UShort
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=257)> _
Public Description As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=129)> _
Public SystemStatus As String
Public MaxSockets As UShort
Public MaxUdpDatagramSize As UShort
Public VendorInfoPointer As IntPtr
End Structure
Private Structure WSAData64
Public Version As UShort
Public HighestVersion As UShort
Public MaxSockets As UShort
Public MaxUdpDatagramSize As UShort
Public VendorInfoPointer As IntPtr
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=257)> _
Public Description As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=129)> _
Public SystemStatus As String
End Structure
BTW: cannot use any new library as I am in a legacy maintenance project where I must change as less code as possible.