Hello,
I´m stuck with my knowledge regarding receiving registry values remotely so I wanto to ask for help.
My situation is as follows:
I have a computer A and a computer B which are domain members. From computer A I want to read registry values from a remote PC, in this case computer B. To get general remote access to computer B I have to use my domain administrator account (I need to use my administrator account for every remote action).
My Windows Forms application will not be started with administrative credentials so I have to provide these credentials for every remote action. I tried this (the "Platform Version" key is an own created one which can be read successfully remotely via regedit.exe as administrator):
Dim RegKey As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, computerB, Microsoft.Win32.RegistryView.Registry64).OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall\Platform Version", False)
But this doesn´t work because of the missing possibility to provide credentials. Later I´ve read that it is not possible to get registry values remotely with different credentials because there is no chance to provide them as a parameter.
My next idea was to use WMI. For that there is the following Sub:
Private Sub CreateRemoteProcess(ByVal strComputer As String, ByVal strProcess As String)
Dim MC As Management.ManagementClass = New Management.ManagementClass("Win32_Process")
Dim MBO1 As Management.ManagementBaseObject = MC.GetMethodParameters("Create")
Dim MS As Management.ManagementScope
MBO1("CurrentDirectory") = Nothing
MBO1("CommandLine") = strProcess
MS = New Management.ManagementScope("\\" & strComputer & "\root\cimv2", New Management.ConnectionOptions With {.Username = AdminUsername, .SecurePassword = AdminPassword})
MS.Connect()
MC.Scope = MS
Dim IMO As Management.InvokeMethodOptions = New Management.InvokeMethodOptions(Nothing, System.TimeSpan.MaxValue)
Dim MBO2 As Management.ManagementBaseObject = MC.InvokeMethod("Create", MBO1, Nothing)
End Sub
This time I want to query the registry with reg.exe, pipe the output to a TXT file and copy this TXT file from computer B to computer A. I call the above Sub two times:
CreateRemoteProcess("computerB", "C:\Windows\System32\cmd.exe /c reg query ""HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Platform Version"" /v DisplayVersion > C:\Windows\Temp\PTVersion.txt")
CreateRemoteProcess("computerB", "C:\Windows\System32\cmd.exe /c copy C:\Windows\Temp\PTVersion.txt \\computerA\c$\unbackedup_data /y")
The first call will proceeded as expected and the file will be created but the second line doesn´t work and I don´t know why, there is no exception.
My next idea was to create a BAT file which I´m copying to computer B before calling the Sub:
IO.File.Copy("C:\Windows\Temp\PTVersion.bat", "\\computerB\Windows\Temp", True)
CreateRemoteProcess("computerB", "C:\Windows\System32\cmd.exe /c C:\Windows\Temp\PTVersion.bat")
The BAT file contains the reg.exe and copy command (the two calls above). If I call this the cmd.exe is appearing and disappearing in the Task Manager (in administrator context) but does only the reg.exe query and doesn´t copy the file to computer A.
My next idea was to use a process:
Dim psi As New ProcessStartInfo
With psi
.FileName = "C:\Windows\System32\cmd.exe"
.Arguments = "/c copy \\computerB\c$\windows\temp\PTVersion.txt C:\unbackedup_data /y"
.UserName = AdminUsername
.Password = AdminPassword
.UseShellExecute = False
End With
Process.Start(psi)
But here I got an exception "The stub got wrong data" and don´t understand what this means. BTW this exception occurs as soon as I provide a username and a password for process "psi".
Does anyone have an idea why the file will not be copied to computer A? Or why I get the exception while using a process? My goal is to query the information without using external tools like psexec.
Thank you very much in advance!
Michael
UPDATE 01/28/2019
I added the property ".Verb" to the process and created an additional EXE file which will be started locally and queries the registry key remotely:
Dim psi As New ProcessStartInfo
With psi
.FileName = Application.StartupPath & "\CC_Console.exe"
.Domain = DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain().Name
.UserName = AdminUsername
.Password = AdminPassword
.UseShellExecute = False
.LoadUserProfile = False
.Verb = "runas"
End With
Process.Start(psi).WaitForExit()
This solution works now and I decided to use this way if there is no other chance. I tried to set ".FileName" to "cmd.exe" which is also working but it is not possible to hide the CMD window, even with ".WindowStyle = ProcessWindowStyle.Hidden" and/or ".CreateNoWindow = True".
If someone knows how to hide the CMD window when calling cmd.exe in a process, it would be great to know how.
UPDATE 01/29/2019
I translated the code from
here to VB.Net:
Imports BOOL = System.Int32
Public Class NetworkShare
Private Declare Function WNetAddConnection2A Lib "mpr.dll" (ByRef refNetResource As NetResource, ByVal inPassword As String, ByVal inUsername As String, ByVal inFlags As Integer) As Integer
Private Declare Function WNetCancelConnection2A Lib "mpr.dll" (ByVal inServer As String, ByVal inFlags As Integer, ByVal inForce As Integer)
Private _Server As String
Private _Share As String
Private _DriveLetter As String = Nothing
Private _Username As String = Nothing
Private _Password As String = Nothing
Private _Flags As Integer = 0
Private _NetResource As NetResource = New NetResource
Private _AllowDisconnectWhenInUse As BOOL = 0
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal inServer As String, ByVal inShare As String)
MyBase.New()
_Server = inServer
_Share = inShare
End Sub
Public Sub New(ByVal inServer As String, ByVal inShare As String, ByVal inDriveLetter As String)
MyBase.New()
_Server = inServer
_Share = inShare
_DriveLetter = inDriveLetter
End Sub
Public Sub New(ByVal inServer As String, ByVal inShare As String, ByVal inUsername As String, ByVal inPassword As String)
MyBase.New()
_Server = inServer
_Share = inShare
_Username = inUsername
_Password = inPassword
End Sub
Public Sub New(ByVal inServer As String, ByVal inShare As String, ByVal inDriveLetter As String, ByVal inUsername As String, ByVal inPassword As String)
MyBase.New()
_Server = inServer
_Share = inShare
_DriveLetter = inDriveLetter
_Username = inUsername
_Password = inPassword
End Sub
Public Property Server As String
Get
Return _Server
End Get
Set(value As String)
_Server = value
End Set
End Property
Public Property Share As String
Get
Return _Share
End Get
Set(value As String)
_Share = value
End Set
End Property
Public ReadOnly Property FullPath As String
Get
Return String.Format("\\{0}\{1}", _Server, _Share)
End Get
End Property
Public Property DriveLetter As String
Get
Return _DriveLetter
End Get
Set(value As String)
SetDriveLetter(value)
End Set
End Property
Public Property Username As String
Get
If String.IsNullOrEmpty(_Username) Then
Return Nothing
Else
Return _Username
End If
End Get
Set(value As String)
_Username = value
End Set
End Property
Public Property Password As String
Get
If String.IsNullOrEmpty(_Password) Then
Return Nothing
Else
Return _Password
End If
End Get
Set(value As String)
_Password = value
End Set
End Property
Public Property Flags As Integer
Get
Return _Flags
End Get
Set(value As Integer)
_Flags = value
End Set
End Property
Public Property Resource As NetResource
Get
Return Me._NetResource
End Get
Set(value As NetResource)
Me._NetResource = value
End Set
End Property
Public Property AllowDisconnectWhenInUse As Boolean
Get
Return Convert.ToBoolean(_AllowDisconnectWhenInUse)
End Get
Set(value As Boolean)
_AllowDisconnectWhenInUse = Convert.ToInt32(value)
End Set
End Property
Public Function Connect() As Integer
_NetResource.Scope = CType(Scope.RESOURCE_GLOBALNET, Integer)
_NetResource.Type = CType(Type.RESOURCETYPE_DISK, Integer)
_NetResource.DisplayType = CType(DisplayType.RESOURCEDISPLAYTYPE_SHARE, Integer)
_NetResource.Usage = CType(Usage.RESOURCEUSAGE_CONNECTABLE, Integer)
_NetResource.RemoteName = FullPath
_NetResource.LocalName = DriveLetter
Return WNetAddConnection2A(_NetResource, _Password, _Username, _Flags)
End Function
Public Function Disconnect() As Integer
Dim retVal As Integer = 0
If (_DriveLetter IsNot Nothing) Then
retVal = WNetCancelConnection2A(_DriveLetter, _Flags, _AllowDisconnectWhenInUse)
retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse)
Else
retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse)
End If
Return retVal
End Function
Private Sub SetDriveLetter(ByVal inString As String)
If inString.Length = 1 Then
If Char.IsLetter(inString.ToCharArray()(0)) Then
_DriveLetter = inString & ":"
Else
_DriveLetter = Nothing
End If
ElseIf inString.Length = 2 Then
Dim drive As Char() = inString.ToCharArray()
If (Char.IsLetter(drive(0)) AndAlso (drive(1) = Microsoft.VisualBasic.ChrW(58))) Then
_DriveLetter = inString
Else
_DriveLetter = Nothing
End If
Else
_DriveLetter = Nothing
End If
End Sub
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> _
Public Structure NetResource
Public Scope As UInteger
Public Type As UInteger
Public DisplayType As UInteger
Public Usage As UInteger
Public LocalName As String
Public RemoteName As String
Public Comment As String
Public Provider As String
End Structure
Public Enum Scope
RESOURCE_CONNECTED = 1
RESOURCE_GLOBALNET
RESOURCE_
RESOURCE_RECENT
RESOURCE_CONTEXT
End Enum
Public Enum Type As UInteger
RESOURCETYPE_ANY
RESOURCETYPE_DISK
RESOURCETYPE_PRINT
RESOURCETYPE_RESERVED = 8
RESOURCETYPE_UNKNOWN = 4294967295
End Enum
Public Enum DisplayType
RESOURCEDISPLAYTYPE_GENERIC
RESOURCEDISPLAYTYPE_DOMAIN
RESOURCEDISPLAYTYPE_SERVER
RESOURCEDISPLAYTYPE_SHARE
RESOURCEDISPLAYTYPE_FILE
RESOURCEDISPLAYTYPE_GROUP
RESOURCEDISPLAYTYPE_NETWORK
RESOURCEDISPLAYTYPE_ROOT
RESOURCEDISPLAYTYPE_SHAREADMIN
RESOURCEDISPLAYTYPE_DIRECTORY
RESOURCEDISPLAYTYPE_TREE
RESOURCEDISPLAYTYPE_NDSCONTAINER
End Enum
Public Enum Usage As UInteger
RESOURCEUSAGE_CONNECTABLE = 1
RESOURCEUSAGE_CONTAINER = 2
RESOURCEUSAGE_NOLOCALDEVICE = 4
RESOURCEUSAGE_SIBLING = 8
RESOURCEUSAGE_ATTACHED = 16
RESOURCEUSAGE_ALL = 31
RESOURCEUSAGE_RESERVED = 2147483648
End Enum
Public Enum ConnectionFlags As UInteger
CONNECT_UPDATE_PROFILE = 1
CONNECT_UPDATE_RECENT = 2
CONNECT_TEMPORARY = 4
CONNECT_INTERACTIVE = 8
CONNECT_PROMPT = 16
CONNECT_NEED_DRIVE = 32
CONNECT_REFCOUNT = 64
CONNECT_REDIRECT = 128
CONNECT_LOCALDRIVE = 256
CONNECT_CURRENT_MEDIA = 512
CONNECT_DEFERRED = 1024
CONNECT_COMMANDLINE = 2048
CONNECT_CMD_SAVECRED = 4096
CONNECT_CRED_RESET = 8192
CONNECT_RESERVED = 4278190080
End Enum
End Class
The value from the registry was get but unfortunately I got an "MarshalDirectiveException" exception: "PInvoke restriction: cannot return variants."
The Program.cs is:
Module Module1
Sub Main()
Dim ServerName As String = "computer"
Dim share As NetworkShare = New NetworkShare(ServerName, "C$", "admin", "password")
share.Connect()
Dim ProductName As String = String.Empty
Dim key As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, ServerName)
If key IsNot Nothing Then
key = key.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion")
If key IsNot Nothing Then
ProductName = key.GetValue("ProductName").ToString()
End If
End If
Console.WriteLine("The device " & ServerName & " is running " & ProductName & ".")
share.Disconnect()
End Sub
End Module
What I have tried:
I explained my troubleshooting steps in the question.