Michael____ Ответов: 1

Как получить значения реестра с удаленного компьютера?


Привет,

Я застрял со своими знаниями о получении значений реестра удаленно, поэтому я хочу попросить о помощи.

Моя ситуация выглядит следующим образом:

У меня есть компьютер а и компьютер в, которые являются членами домена. С компьютера а я хочу прочитать значения реестра с удаленного компьютера, в данном случае компьютера В. Чтобы получить общий удаленный доступ к компьютеру B, я должен использовать свою учетную запись администратора домена (мне нужно использовать свою учетную запись администратора для каждого удаленного действия).

Мое приложение Windows Forms не будет запущено с административными учетными данными, поэтому я должен предоставить эти учетные данные для каждого удаленного действия. Я попробовал это (ключ "версия платформы" - это собственный созданный ключ, который можно успешно прочитать удаленно через regedit.exe как администратор):
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)
Но это не работает из-за отсутствия возможности предоставить учетные данные. Позже я прочитал, что невозможно получить значения реестра удаленно с разными учетными данными, потому что нет никакой возможности предоставить их в качестве параметра.

Моей следующей идеей было использовать WMI. Для этого существует следующая подзаголовок:
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
На этот раз я хочу запросить реестр с помощью reg.exe, передайте выходные данные в TXT-файл и скопируйте этот TXT-файл с компьютера B на компьютер A. Я вызываю вышеупомянутую субмарину два раза:
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")
Первый вызов будет продолжен, как и ожидалось, и файл будет создан, но вторая строка не работает, и я не знаю, почему, нет никакого исключения.

Моя следующая идея состояла в том, чтобы создать BAT-файл, который я копирую на компьютер B перед вызовом 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")
Файл BAT содержит следующее reg.exe и скопируйте команду (два вызова выше). Если я назову это cmd.exe появляется и исчезает в Диспетчере задач (в контексте администратора), но делает только то, что reg.exe запрос и не копирует файл на компьютер A.

Моей следующей идеей было использовать процесс:
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)
Но здесь я получил исключение "заглушка получила неправильные данные" и не понимаю, что это значит. Кстати, это исключение возникает, как только я предоставляю имя пользователя и пароль для процесса "psi".

Есть ли у кого-нибудь идея, почему файл не будет скопирован на компьютер а? Или почему я получаю исключение при использовании процесса? Моя цель-запросить информацию без использования внешних инструментов, таких как psexec.

Заранее Вам большое спасибо!

Майкл


ОБНОВЛЕНИЕ 01/28/2019

Я добавил свойство ".Verb" в процесс и создал дополнительный EXE-файл, который будет запускаться локально и запрашивать раздел реестра удаленно:
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()
Это решение работает сейчас, и я решил использовать этот способ, если нет другого шанса. Я попытался установить ".FileName" на "cmd.exe" который также работает, но невозможно скрыть окно CMD, даже с помощью ".WindowStyle = ProcessWindowStyle.Скрыто" и/или ".CreateNoWindow = True".

Если кто-то знает, как скрыть окно CMD при вызове cmd.exe в процессе, было бы здорово знать, как.


ОБНОВЛЕНИЕ 01/29/2019

Я перевел код с английского языка. здесь в 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)   '<-- 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
Значение из реестра было get, но, к сожалению, я получил исключение "MarshalDirectiveException": "ограничение PInvoke: не может возвращать варианты."

Программа.cs-это:
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


Что я уже пробовал:

Я объяснил свои действия по устранению неполадок в этом вопросе.

Richard Deeming

Хранение учетных данных администратора домена внутри приложения звучит как плохая идея. Помимо проблем, связанных с изменением или истечением срока действия вашего пароля, существует очень реальная опасность того, что пользователь, не являющийся администратором, имеющий доступ к вашему приложению, может декомпилировать его и получить доступ администратора домена, используя ваши учетные данные.

Почему бы не создать второе приложение, которое должно быть запущено с повышенными правами, и не запустить его из вашего основного приложения?

Michael____

Заявление имеет две формы. При каждом запуске приложения первая форма запрашивает у пользователя учетные данные администратора. Эта информация будет храниться в открытых переменных второй формы. После закрытия приложения учетные данные исчезают. Учетные данные не будут сохранены в файл.

Что касается вашего предложения: я уже пробовал это и хотел запустить второе приложение локально как процесс, но я получаю исключение "заглушка получила неправильные данные". Сами учетные данные (например, неправильный пароль) не могут быть причиной, потому что я могу сделать запрос WMI с тем же самым, и это работает.

pdoxtader

Майкл,
Entering your domain administrator credentials into a .NET application and walking away from it is serious security problem. I don't know if you are aware of this, but strings are never destroyed in .net applications, and they are visible to anyone who has a clue. For instance, Process Explorer from Sysinternals is free and doesn't need to be installed. You just download it and run it. It works and looks very much like the windows Task Manager - except you have a few nice added tools for more computer savvy people (and network admins) - like the ability to see all strings in any running application. Including your administrator password. Think about it. Just because you didn't know about this doesn't mean that the user sitting at your workstation doesn't either.

Правильный способ сделать это-вместо этого создать службу windows и запустить ее с самым низким уровнем разрешений, который вы, возможно, можете, и все же получить то, что вам нужно. Посмотрите здесь на codeproject для библиотек tcp/ip (у меня есть два, на которые вы можете посмотреть), и создайте себе сетевое корпоративное приложение, которое делает то, что вам нужно, и передает любые файлы или данные на вашу машину.

Michael____

Большое вам спасибо за объяснение! Я знаю Process Explorer, но я не знал, что вы можете видеть строки с ним. Это заставляет меня задуматься...

Идея с сервисом хороша, и я могу устанавливать службы, но, к сожалению, мне это не разрешено, потому что я использую приложение в корпоративной среде и не могу установить/настроить то, что я хочу, на компьютерах клиентов.

Dave Kreskowiak

CreateRemoteProcess("computerB", "C:\Windows\System32\cmd.exe /C копировать C:\Windows\Temp\PTVersion.txt \\компьютер\с$\unbackedup_data /г")

Первый вызов будет продолжен, как и ожидалось, и файл будет создан, но вторая строка не работает, и я не знаю, почему, нет никакого исключения.


Это не работает, потому что удаленный процесс, который вы создали, не будет иметь admin creditials, чтобы пройти мимо C$ (Admin share!) ComputerA, чтобы скопировать файл обратно.

WMI-это способ пойти, если вам нужно ввести учетные данные, но вы никогда не говорили, с какой проблемой вы столкнулись, используя его.

1 Ответов

Рейтинг:
9

Dave Kreskowiak

См. Мой комментарий выше про сторону WMI, чтобы сделать это. Вы никогда не упоминали о проблеме с этим методом.

Но есть способ заставить классы реестра .NET работать удаленно и с предоставленными учетными данными. Видеть Rhyous » архив блога » как для проверки подлинности и доступа к реестру удаленно, используя C#[^].


Michael____

Привет, Дэйв, спасибо за ссылку, это звучит интересно и может быть решением в дальнейших проектах. Я преобразовал его в VB.Net и это работает (он получает значение и возвращает его), но я получаю исключение в классе "NetworkShare.cs" строка 166. Он говорит мне: "MarshalDirectiveException: PInvoke restriction: не может возвращать варианты".. У вас есть идея, как ее решить?

Кстати: я не могу упомянуть о проблеме, которую я не понимаю.

Dave Kreskowiak

Учитывая, что я понятия не имею, как выглядит ваш преобразованный код, я понятия не имею.

Michael____

Извините, я забыл упомянуть, что строка 166 была строкой в проекте C# из предложенной Вами ссылки. Я добавил переведенный код к вышеприведенному вопросу.

Dave Kreskowiak

Во втором заголовке функции импорта DLL не указан тип возвращаемого значения.

Private Declare Function WNetCancelConnection2A Lib "mpr.dll" (ByVal inServer As String, ByVal inFlags As Integer, ByVal inForce As Integer) As ??????????

Глядя на исходный код C#, он должен быть целым числом.

Michael____

ЧТО ЗА ЧЕРТ ???

Я добавил возвращаемый тип, и исключение исчезло. Приложение уже работает.

Большое вам спасибо за вашу помощь! Ваше предложение принимается как решение.

Dave Kreskowiak

У Sub никогда не бывает возвращаемых типов. Функция всегда должна иметь их заданными.

Michael____

- Да, я знаю. Я просто забыл об этом, и Visual Studio не показывает намека на это. Но все равно спасибо!