Click here to Skip to main content
15,892,643 members

How to get registry values from remote computer?

Michael____ asked:

Open original thread
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):
VB
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:
VB
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:
VB
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:
VB
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:
VB
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:
VB
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:
VB
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)   '<-- here comes an exception
        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_REMEMBERED
        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:
VB
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.
Tags: Visual Basic, Registry, Remote

Plain Text
ASM
ASP
ASP.NET
BASIC
BAT
C#
C++
COBOL
CoffeeScript
CSS
Dart
dbase
F#
FORTRAN
HTML
Java
Javascript
Kotlin
Lua
MIDL
MSIL
ObjectiveC
Pascal
PERL
PHP
PowerShell
Python
Razor
Ruby
Scala
Shell
SLN
SQL
Swift
T4
Terminal
TypeScript
VB
VBScript
XML
YAML

Preview



When answering a question please:
  1. Read the question carefully.
  2. Understand that English isn't everyone's first language so be lenient of bad spelling and grammar.
  3. If a question is poorly phrased then either ask for clarification, ignore it, or edit the question and fix the problem. Insults are not welcome.
  4. Don't tell someone to read the manual. Chances are they have and don't get it. Provide an answer or move on to the next question.
Let's work to help developers, not make them feel stupid.
Please note that all posts will be submitted under the http://www.codeproject.com/info/cpol10.aspx.



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900