Как получить значения реестра с удаленного компьютера?
Привет,
Я застрял со своими знаниями о получении значений реестра удаленно, поэтому я хочу попросить о помощи.
Моя ситуация выглядит следующим образом:
У меня есть компьютер а и компьютер в, которые являются членами домена. С компьютера а я хочу прочитать значения реестра с удаленного компьютера, в данном случае компьютера В. Чтобы получить общий удаленный доступ к компьютеру 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-это способ пойти, если вам нужно ввести учетные данные, но вы никогда не говорили, с какой проблемой вы столкнулись, используя его.