Reputation: 13475
I am using Delphi to remotely read and write the registry of a remote machine. This works when my account on my machine has admin access to the remote machine.
However, I'd like to be able to specify a username / pwd when connecting to read the registry so I can connect with alternate credentials.
With the file-system I called the following (with the username and password) and was able to establish a connection to the remote system and perform filesystem related functions. However, this does not appear to work with the registry.
var
netResource : TNetResource;
begin
FillChar(netResource, SizeOf(netResource), 0);
netResource.dwScope := RESOURCE_GLOBALNET;
netResource.dwType := RESOURCETYPE_DISK;
netResource.dwDisplayType := RESOURCEDISPLAYTYPE_SHARE;
netResource.dwUsage := RESOURCEUSAGE_CONNECTABLE;
netResource.lpRemoteName := PChar('\\192.168.1.105\IPC$');
WNetAddConnection2(netResource, PChar(password), PChar(username), 0);
end;
...
And here is the example of the function I'd like to be able to call, but specify the credentials with access to the remote machine:
procedure TForm1.SetWallpaperKey() ;
var
reg:TRegistry;
begin
reg:=TRegistry.Create;
with reg do begin
try
if RegistryConnect('192.168.1.105') then
if OpenKey('\Control Panel\desktop', False) then begin
//change wallpaper and tile it
reg.WriteString ('Wallpaper','c:\windows\CIRCLES.bmp') ;
reg.WriteString ('TileWallpaper','1') ;
//disable screen saver//('0'=disable, '1'=enable)
reg.WriteString('ScreenSaveActive','0') ;
end
finally
reg.Free;
end;
end;
end;
Upvotes: 5
Views: 6138
Reputation: 378
In case anyone wants to use WNetAddConnection2 correctly in C#, here's some code. The problem is WNetAddConnection2 expects an IntPtr which holds the buffer of the NETRESOURCE, otherwise you get return code of 487 (0x1E7) which is ERROR_INVALID_ADDRESS. Attempt to access invalid address.
I personally was trying to get registry key that honored elevated privileges and thought by connecting to IPC$ it would work, but of course not, seems only way is using RegCreateKeyEx with the base key from RegConnectRegistry... which is acceptable as long as it actually works once I get around to it, haha!
public class IPCShareSession : IDisposable
{
#region PInvoke Functions,Structs,Enums
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
private static extern int WNetAddConnection2(IntPtr NetResource, string Password, string Username, WNetConnect Flags);
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
private static extern int WNetCancelConnection2(string ServerName, WNetDisconnect Flags, bool Force);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class NETRESOURCE
{
public WNetResourceScope dwScope = 0;
public WNetResourceType dwType = 0;
public WNetResourceDisplayType dwDisplayType = 0;
public WNetResourceUsage dwUsage = 0;
public string lpLocalName = null;
public string lpRemoteName = null;
public string lpComment = null;
public string lpProvider = null;
};
private enum WNetResourceScope
{
Connected = 0x1,
GlobalNetwork = 0x2,
Remembered = 0x3,
Recent = 0x4,
Context = 0x5
}
public enum WNetResourceType
{
Any = 0,
Disk = 1,
Print = 2,
Reserved = 8
}
private enum WNetResourceUsage
{
Connectable = 0x1,
Container = 0x2,
NoLocalDevice = 0x4,
Sibling = 0x8,
Attached = 0x10,
All = Connectable | Container | Attached
}
private enum WNetResourceDisplayType
{
Generic = 0x0,
Domain = 0x1,
Server = 0x2,
Share = 0x3,
File = 0x4,
Group = 0x5,
Network = 0x6,
Root = 0x7,
ShareAdmin = 0x8,
Directory = 0x9,
Tree = 0xa,
NDSContainer = 0xb
}
private enum WNetConnect : int
{
CONNECT_UPDATE_PROFILE = 0x00000001,
CONNECT_UPDATE_RECENT = 0x00000002,
CONNECT_TEMPORARY = 0x00000004,
CONNECT_INTERACTIVE = 0x00000008,
CONNECT_PROMPT = 0x00000010,
CONNECT_REDIRECT = 0x00000080,
CONNECT_CURRENT_MEDIA = 0x00000200,
CONNECT_COMMANDLINE = 0x00000800,
CONNECT_CMD_SAVECRED = 0x00001000,
CONNECT_CRED_RESET = 0x00002000
}
private enum WNetDisconnect
{
DISCONNECT=0,
DISCONNECT_AND_REMOVE=1
}
#endregion
private string ServerShare = null;
private NETRESOURCE serverNR = null;
private bool bConnected = false;
public IPCShareSession(string ServerName, string ShareName = "IPC$")
{
ServerShare = string.Format(@"\\{0}\{1}", ServerName, ShareName);
//Setup nr to be a IPC share session
serverNR = new NETRESOURCE
{
dwType = WNetResourceType.Disk,
lpLocalName = null,
lpRemoteName = ServerShare,
lpProvider = null,
dwDisplayType = WNetResourceDisplayType.ShareAdmin,
dwUsage = WNetResourceUsage.Connectable,
};
}
public int Connect()
{
if(!bConnected)
{
IntPtr pNR = Marshal.AllocHGlobal(Marshal.SizeOf(serverNR));
Marshal.StructureToPtr(serverNR, pNR, false);
int ret = WNetAddConnection2(pNR, null, null, WNetConnect.CONNECT_TEMPORARY);
//Free our unmanaged pointer now that we're done with it.
Marshal.FreeHGlobal(pNR);
bConnected = ret == 0;
return ret;
}
else
{
return -1;
}
}
public int Disconnect()
{
int ret = 0;
if (bConnected)
{
ret = WNetCancelConnection2(ServerShare, WNetDisconnect.DISCONNECT, true);
bConnected = false;
}
else
{
ret = -1;
}
return ret;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (bConnected)
{
Disconnect();
}
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
Upvotes: 0
Reputation: 136391
Now using the windows API , the TRegistry.RegistryConnect
function internally call the RegConnectRegistry
windows function , the documentation says :
If the current user does not have proper access to the remote computer, the call to RegConnectRegistry fails. To connect to a remote registry, call LogonUser with LOGON32_LOGON_NEW_CREDENTIALS and ImpersonateLoggedOnUser before calling RegConnectRegistry.
Windows 2000: One possible workaround is to establish a session
to an administrative share such as IPC$ using a different set of credentials. To specify credentials other than those of the current user, use the WNetAddConnection2 function to connect to the share. When you have finished accessing the registry, cancel the connection.
Windows XP Home Edition: You cannot use this function to connect to
a remote computer running Windows XP Home Edition. This function does work with the name of the local computer even if it is running Windows XP Home Edition because this bypasses the authentication layer.
check the next samples
uses
Windows,
Registry,
SysUtils;
procedure AccessRemoteRegistry(const lpMachineName, lpszUsername , lpszPassword: PChar);
const
LOGON32_LOGON_NEW_CREDENTIALS = 9;
REG_OPTION_OPEN_LINK = $00000008;
var
netResource : TNetResource;
dwFlags : DWORD;
dwRetVal : DWORD;
phToken : THandle;
phkResult : HKEY;
phkResult2 : HKEY;
lpType : DWORD;
lpData : PByte;
lpcbData : DWORD;
begin
ZeroMemory(@netResource, SizeOf(netResource));
netResource.dwType := RESOURCETYPE_ANY;
netResource.lpLocalName := nil;
netResource.lpRemoteName:= lpMachineName;
netResource.lpProvider := nil;
dwFlags := CONNECT_UPDATE_PROFILE;
dwRetVal := WNetAddConnection2(netResource, lpszPassword, lpszUsername, dwFlags);
if dwRetVal=NO_ERROR then
begin
try
Writeln('Connected');
if LogonUser(lpszUsername, nil, lpszPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50,phToken) then
begin
try
if ImpersonateLoggedOnUser(phToken) then
begin
try
dwRetVal:=RegConnectRegistry(lpMachineName,HKEY_LOCAL_MACHINE,phkResult);
if dwRetVal = ERROR_SUCCESS then
begin
dwRetVal:=RegOpenKeyEx(phkResult,PChar('SOFTWARE\Borland\Delphi\5.0'), REG_OPTION_OPEN_LINK, KEY_QUERY_VALUE, phkResult2);
if dwRetVal = ERROR_SUCCESS then
begin
try
lpType:=REG_SZ;
//get the size of the buffer
dwRetVal:=RegQueryValueEx(phkResult2, PChar('App'), nil, @lpType, nil, @lpcbData);
if dwRetVal = ERROR_SUCCESS then
begin
GetMem(lpData,lpcbData);
try
dwRetVal:=RegQueryValueEx(phkResult2, 'App', nil, @lpType, lpData, @lpcbData);
if dwRetVal = ERROR_SUCCESS then
WriteLn(PChar(lpData))
else
Writeln(Format('RegQueryValueEx error %d',[dwRetVal]));
finally
FreeMem(lpData);
end;
end
else
Writeln(Format('RegQueryValueEx error %d',[dwRetVal]));
finally
RegCloseKey(phkResult2);
end;
end
else
Writeln(Format('RegOpenKeyEx error %d',[dwRetVal]));
end
else
Writeln(Format('RegConnectRegistry error %d',[dwRetVal]));
finally
RevertToSelf;
end;
end
else
RaiseLastOSError;
finally
CloseHandle(phToken);
end;
end
else
RaiseLastOSError;
finally
dwRetVal:=WNetCancelConnection2(netResource.lpRemoteName, CONNECT_UPDATE_PROFILE, false);
if dwRetVal<>NO_ERROR then
Writeln(Format('WNetCancelConnection2 error %d',[dwRetVal]));
end;
end
else
Writeln(Format('WNetAddConnection2 Connection error %d',[dwRetVal]));
end;
use in this way
AccessRemoteRegistry('\\192.168.52.128','NormalUser','password');
procedure AccessRemoteRegistry2(const lpMachineName, lpszUsername , lpszPassword: PChar);
const
LOGON32_LOGON_NEW_CREDENTIALS = 9;
REG_OPTION_OPEN_LINK = $00000008;
var
netResource : TNetResource;
dwFlags : DWORD;
dwRetVal : DWORD;
phToken : THandle;
Reg : TRegistry;
begin
ZeroMemory(@netResource, SizeOf(netResource));
netResource.dwType := RESOURCETYPE_ANY;
netResource.lpLocalName := nil;
netResource.lpRemoteName:= lpMachineName;
netResource.lpProvider := nil;
dwFlags := CONNECT_UPDATE_PROFILE;
dwRetVal := WNetAddConnection2(netResource, lpszPassword, lpszUsername, dwFlags);
if dwRetVal=NO_ERROR then
begin
try
Writeln('Connected');
if LogonUser(lpszUsername, nil, lpszPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50,phToken) then
begin
try
if ImpersonateLoggedOnUser(phToken) then
begin
try
reg:=TRegistry.Create;
try
reg.RootKey:=HKEY_LOCAL_MACHINE;
if reg.RegistryConnect(lpMachineName) then
if reg.OpenKey('SOFTWARE\Borland\Delphi\5.0',False) then
WriteLn(reg.ReadString('App'));
finally
reg.CloseKey;
reg.Free;
end;
finally
RevertToSelf;
end;
end
else
RaiseLastOSError;
finally
CloseHandle(phToken);
end;
end
else
RaiseLastOSError;
finally
dwRetVal:=WNetCancelConnection2(netResource.lpRemoteName, CONNECT_UPDATE_PROFILE, false);
if dwRetVal<>NO_ERROR then
Writeln(Format('WNetCancelConnection2 error %d',[dwRetVal]));
end;
end
else
Writeln(Format('WNetAddConnection2 Connection error %d',[dwRetVal]));
end;
use in this way
AccessRemoteRegistry2('\\192.168.52.128','NormalUser','password');
Upvotes: 3
Reputation: 136391
Mick , i can't resist give you a WMI solution for you problem ;) , the wmi have a class called StdRegProv
which allow you to access the registry in local and remote machines. A key point is the namespace where the class is located, that depends of the version of windows installed in the remote machine. so for Windows Server 2003, Windows XP, Windows 2000, Windows NT 4.0, and Windows Me/98/95 the StdRegProv
class is available in the root\default
namespace and for others versions like windows Vista/7 the namespace is root\CIMV2
.
Now to configure the credentials to access the registry, you must set these values in the SWbemLocator.ConnectServer
method in this way :
FSWbemLocator.ConnectServer(Server, 'root\default', User, Pass);
Another impor point is which this class just exposes methods to access the registry not properties, so you cannot use a wmi query, instead you must execute wmi methods.
check the next samples to see how it works.
uses
Windows,
SysUtils,
ActiveX,
ComObj;
// The CheckAccess method verifies that the user possesses the specified
// permissions. The method returns a uint32 which is 0 if successful or some other
// value if any other error occurred.
procedure Invoke_StdRegProv_CheckAccess;
const
Server = '192.168.52.128';
User = 'Administrator';
Pass = 'password';
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet : OLEVariant;
FInParams : OLEVariant;
FOutParams : OLEVariant;
begin
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
//http://msdn.microsoft.com/en-us/library/aa393664%28v=vs.85%29.aspx
//StdRegProv is preinstalled in the WMI namespaces root\default and root\cimv2.
//Windows Server 2003, Windows XP, Windows 2000, Windows NT 4.0, and Windows Me/98/95: StdRegProv is available only in root\default namespace.
FWMIService := FSWbemLocator.ConnectServer(Server, 'root\default', User, Pass);
//For Windows Vista or Windows 7 you must use the root\CIMV2 namespace
//FWMIService := FSWbemLocator.ConnectServer(Server, 'root\CIMV2', User, Pass);
FWbemObjectSet:= FWMIService.Get('StdRegProv');
FInParams := FWbemObjectSet.Methods_.Item('CheckAccess').InParameters.SpawnInstance_();
FInParams.hDefKey:=HKEY_LOCAL_MACHINE;
FInParams.sSubKeyName:='SYSTEM\CurrentControlSet';
FInParams.uRequired:=KEY_QUERY_VALUE;
FOutParams := FWMIService.ExecMethod('StdRegProv', 'CheckAccess', FInParams);
Writeln(Format('bGranted %s',[FOutParams.bGranted]));
Writeln(Format('ReturnValue %s',[FOutParams.ReturnValue]));
end;
// The GetStringValue method returns the data value for a named value whose data
// type is REG_SZ.
procedure Invoke_StdRegProv_GetStringValue;
const
Server = '192.168.52.128';
User = 'Administrator';
Pass = 'password';
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet : OLEVariant;
FInParams : OLEVariant;
FOutParams : OLEVariant;
begin
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
//http://msdn.microsoft.com/en-us/library/aa393664%28v=vs.85%29.aspx
//StdRegProv is preinstalled in the WMI namespaces root\default and root\cimv2.
//Windows Server 2003, Windows XP, Windows 2000, Windows NT 4.0, and Windows Me/98/95: StdRegProv is available only in root\default namespace.
FWMIService := FSWbemLocator.ConnectServer(Server, 'root\default', User, Pass);
//For Windows Vista or Windows 7 you must use the root\CIMV2 namespace
//FWMIService := FSWbemLocator.ConnectServer(Server, 'root\CIMV2', User, Pass);
FWbemObjectSet:= FWMIService.Get('StdRegProv');
FInParams := FWbemObjectSet.Methods_.Item('GetStringValue').InParameters.SpawnInstance_();
FInParams.hDefKey:=HKEY_LOCAL_MACHINE;
FInParams.sSubKeyName:='SOFTWARE\Borland\Delphi\5.0';
FInParams.sValueName:='App';
FOutParams := FWMIService.ExecMethod('StdRegProv', 'GetStringValue', FInParams);
Writeln(Format('sValue %s',[FOutParams.sValue]));
Writeln(Format('ReturnValue %s',[FOutParams.ReturnValue]));
end;
// The SetStringValue method sets the data value for a named value whose data type
// is REG_SZ.
procedure Invoke_StdRegProv_SetStringValue;
const
Server = '192.168.52.128';
User = 'Administrator';
Pass = 'password';
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet : OLEVariant;
FInParams : OLEVariant;
FOutParams : OLEVariant;
begin
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(Server, 'root\default', User, Pass);
FWbemObjectSet:= FWMIService.Get('StdRegProv');
FInParams := FWbemObjectSet.Methods_.Item('SetStringValue').InParameters.SpawnInstance_();
FInParams.hDefKey:=HKEY_LOCAL_MACHINE;
FInParams.sSubKeyName:='SOFTWARE\Borland\Delphi\5.0';
FInParams.sValueName:='Dummy';
FInParams.sValue:='ADummyValue';
FOutParams := FWMIService.ExecMethod('StdRegProv', 'SetStringValue', FInParams);
Writeln(Format('ReturnValue %s',[FOutParams.ReturnValue]));
end;
For more options you must check the documentation about this class.
I hope this help you.
Upvotes: 7
Reputation: 68862
Maybe you need WNetAddConnection3? I don't think it enables remote registry though.
Personally though I would look into WMI, and network-named-pipes, which are methods of remote registry access that Windows supports out of the box.
Also, you know about TRegistry.RegistryConnect. It doesn't seem to have the userid/password, thing you want, but I am still not clear why that isn't enough.
Upvotes: 0