Earlier I was using code as below to grab COM+ applications and verify that my app is running
COMAdmin.COMAdminCatalog catalog = new COMAdmin.COMAdminCatalogClass();
catalog.Connect(servername);
catalog.GetCollection("Applications")
Now I need to perform the same actions but from other domain. So when I try to run the code above I receive authentication error.
I have tried to connect via WMI and grab list of COM+ applications from win32 wmi providers, but it seems that it's either not possible or I am doing smth wrong.
I would be pleased if someone could help me to get the list of applications from COMAdminCatalog using credentials.
You will have to impersonate a different user on the current thread.
using (ImpersonatedUser user = new ImpersonatedUser("USER_NAME", "DOMAIN_NAME", "USER PASSWORD"))
{
COMAdmin.COMAdminCatalog objCatalog = new COMAdmin.COMAdminCatalog();
objCatalog.Connect("SERVER_NAME");
COMAdmin.COMAdminCatalogCollection objAppCollection =
(COMAdmin.COMAdminCatalogCollection) objCatalog.GetCollection("Applications");
objAppCollection.Populate();
}
For more details:
ImpersonatedUser class: https://blogs.msdn.microsoft.com/joncole/2009/09/21/impersonation-code-in-c/
How to impersonate: https://blogs.msdn.microsoft.com/shawnfa/2005/03/21/how-to-impersonate/
Related
I have a number of CLI applications which use AD authorization. To simplify user login, credentials are cached in an msalcache file in a known location using code similar to this...
var storageProperties = new StorageCreationPropertiesBuilder(
".msalcache.bin", _directory)
.WithCacheChangedEvent(_config.ClientId)
.Build();
_cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
_cacheHelper.VerifyPersistence();
Some applications also retrieve secrets from an Azure KeyVault using code similar to this
var client = new SecretClient(vault,
new ChainedTokenCredential(
new ManagedIdentityCredential(),
new AzureCliCredential()
));
Using ManagedIdentityCredential allows the code to operate from within a deployed application. AzureCliCredential is used to allow the user access from the CLI. However, as the name implies, it requires the Azure CLI module to be installed and for the user to have separately run az login.
It seems that there ought to be a way to reuse the stored credentials from the msalcache file to create a suitable TokenCredential to provide to the ChainedTokenCredential constructor. I.e. I'd like to be able to write something like...
var client = new SecretClient(vault,
new ChainedTokenCredential(
new ManagedIdentityCredential(),
new MsalCacheClient(_pathToMsalCacheFile)
));
Can anyone suggest the appropriate way to do this?
I need some help with examples how to use Credential of a current user running application.
So in windows 7 you can run application using user loged in by simply running application or you can use "Run as a different User" option and run it as another user.
In my Active Directory I have 2 account Domain User and one with Domain Admin rights. I'm login Windows as a Domain User and when I need I'm using "Run as a different User" to launch some task as a Domain Admin.
So the task is to get my Credential and use it to perform some task, lets say rename active directory user name.
Best way to do this as I can see is to ask user running application to enter Domain Admin credential on then start application and use them for various task. Of course I can easily run application with "Run as a different User" but I still need to get this credential and use them.
I've searched through the web and I can't find this, all i could find is using credential for a web auth.
If you can show me some examples how to:
1) Ask user for a Admin user credential ( i can leave without this )
2) Get and use credentials of a user running application
I don't want to know password I know I can't. Don't really want to add to a WPF form password box I prefer to use windows API to handle this i've already entered user name and password using "Run as a different User".
PS: I sorry if this topic exists :( I guess I'm bad at creating correct search requests.
ADDED: to be more clear what I need. In powershell it will look like this:
# This Asks user to enter credentials
$cred = Get-Credential;
# this checks if I have rights to use them.
Get-ADDomain “DOMAIN” –Server “Domain.com” –Credential $cred;
Of course it's simplified as hell though the point is that I can use credentials user entered when ever it's needed.
The equivalent C# to your Get-ADDomain is quite simple, it is just
public void PerformSomeActionAsAdmin(string adminUsername, string adminPassword)
{
//Null causes the constructor to connect to the current domain the machine is on.
// |
// V
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, null, adminUsername, adminPassword))
{
//do something here with ctx, the operations will be performed as whoever's username and password you passed in.
}
}
if you don't want to connect to the current domain and instead want to connect to Domain.com then replace the null with the appropriate string.
EDIT: if you want to use secure strings you can't use System.DirectoryServices.AccountManagement.PrincipalContext, you will need to go with the lower level calls in System.DirectoryServices.Protocols. Doing this process is quite complex, here is a link to the MSDN article "Introduction to System.DirectoryServices.Protocols (S.DS.P)" explaining how to use it. It is a big complex read and honestly I don't think it is worth it to be able to use encrypted strings.
public void PerformSomeActionAsAdmin(NetworkCredential adminCredential)
{
using(LdapConnection connection = new LdapConnection("fabrikam.com", adminCredential))
{
// MAGIC
}
}
Do you want to check if the current user is a doman admin? start by looking at his code, it should help you get started identifying what AD groups the current user is in. This will give you a list of strings that are each group's name the current user belongs to. Then you can check that list against whatever AD group you are trying to check for. Replace YourDomain with your domain name:
WindowsIdentity wi = WindowIdentity.GetCurrent();
List<string> result = new List<string>();
foreach (IdentityReference group in wi.Groups)
{
result.Add(group.Translate(typeof(NTAccount)).ToString().Replace("YourDomain\\", String.Empty));
}
Since i'm not quite sure what you're trying to do, this also might be helpful. You'd have to get the user name and password from a textobx, password box etc. This could be used for an "override" to use, for example, a manager's credentials etc. to do something the current user wasn't allowed to do because of AD group membership etc.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YourDomain"))
{
if (UserName.Contains("YourDomain\\"))
{
UserName = UserName.Replace("YourDomain\\", String.Empty);
}
//validate the credentials
bool IsValid = pc.ValidateCredentials(UserName, Password);
}
The Problem: When new IIS Application Pools are created and set to use the Application Pool Identity for permissions, I am unsure how to add those identities to User Groups such as Administrator or Performance Counter Users.
The Background: I'm currently writing a C#.NET library which uses Microsoft.Web.Administration in order to do the following:
Detect if IIS 7.x is installed, and if so, what components.
Install or upgrade IIS 7.x to a provided list of required components.
Create/manage one or more web sites through IIS.
Automatically create/manage one application pool per web site
The context is that this library is to be used by executable installers to provide automated deployment of a web server and web sites/services on Windows Server OSes as part of a larger software deployment. So far, all of the above has been implemented, tested, and is (mostly) functional except for the automation of some permissions that need to be performed on Application Pool / Website creation.
In my method for installing a new website, I create a new Application Pool and force it to use the Application Pool Identity:
static public void InstallSite(string name, string path, int port)
{
Site site;
var appPoolName = ApplicationPoolBaseName + name;
using (var iisManager = new ServerManager())
{
// Set up a custom application pool for any site we run.
if (!iisManager.ApplicationPools.Any(pool => pool.Name.Equals(appPoolName)))
{
iisManager.ApplicationPools.Add(appPoolName);
iisManager.ApplicationPools[appPoolName].ManagedRuntimeVersion = "v4.0";
}
iisManager.CommitChanges();
}
// ... other code here ('site' gets initialized) ...
using (var iisManager = new ServerManager())
{
// Set anonymous auth appropriately
var config = iisManager.GetWebConfiguration(site.Name);
var auth = config.GetSection("system.web/authentication");
auth.SetMetadata("mode", "Windows");
var authSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication");
authSection.SetAttributeValue("enabled", true);
authSection.SetAttributeValue("userName", string.Empty); // Forces the use of the Pool's Identity.
authSection = config.GetSection("system.webServer/security/authentication/basicAuthentication");
authSection.SetAttributeValue("enabled", false);
authSection = config.GetSection("system.webServer/security/authentication/digestAuthentication");
authSection.SetAttributeValue("enabled", false);
authSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication");
authSection.SetAttributeValue("enabled", false);
iisManager.CommitChanges();
}
// ... other code here ...
}
As I understand it, this would be the best security practice, and I would then add permissions to specific web sites for anything more than minimal system access. Part of this process would be to add these Application Pool identities to User Groups, such as Administrator or Performance Monitor Users. This is where complications arise.
Now, as documented elsewhere, each Application Pool Identity exists in the format of IIS AppPool\\<pool_name> but this faux-user is not listed through the normal GUI user management controls, and does not seem to be accessible through libraries such as System.DirectoryServices.AccountManagement when following this example on SO. Also, other questions about the Application Pool Identity seem to relate to referencing it from within a child website, not from within an installation context.
So, does anyone know what the proper methods are for
a) Referencing and accessing Application Pool Identities programmatically.
b) Giving Application Pool Identities permissions by adding them User Groups.
Thanks for your well-written question. It is exactly the problem that I was trying to solve last night and it gave me enough to go on that I was able finally cobble together an answer that uses only managed code. There were three steps that I found to getting the framework to find and work with the virtual user:
using new System.Security.Principal.NTAccount(#"IIS APPPOOL\<appPoolName>") to get a handle on the account.
using .Translate(typeof (System.Security.Principal.SecurityIdentifier)) to convert it to a SID
understanding that Principal.FindByIdentity() treats that SID like it is a group, rather than a user
A final working program (Windows Server 2012 for my test) is as follows:
using System;
using System.DirectoryServices.AccountManagement;
namespace WebAdminTest
{
internal class Program
{
private static void Main(string[] args)
{
var user = new System.Security.Principal.NTAccount(#"IIS APPPOOL\10e6c294-9836-44a9-af54-207385846ebf");
var sid = user.Translate(typeof (System.Security.Principal.SecurityIdentifier));
var ctx = new PrincipalContext(ContextType.Machine);
// This is weird - the user SID resolves to a group prinicpal, but it works that way.
var appPoolIdentityGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sid.Value);
Console.WriteLine(appPoolIdentityGroupPrincipal.Name);
Console.WriteLine(appPoolIdentityGroupPrincipal.DisplayName);
GroupPrincipal targetGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, "Performance Monitor Users");
// Making appPoolIdentity "group" a member of the "Performance Monitor Users Group"
targetGroupPrincipal.Members.Add(appPoolIdentityGroupPrincipal);
targetGroupPrincipal.Save();
Console.WriteLine("DONE!");
Console.ReadKey();
}
}
}
A solution presented itself sooner than I expected, though it's not the one I preferred. For anyone interested, there are a couple of additional options on this pinvoke page. The managed solution did not work for me, but the sample using DllImport worked. I ended up adjusting the sample to handle arbitrary groups based on mapping an enum to SID strings, and including another DllImport for:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
The modified (working) function looks something like this:
static public bool AddUserToGroup(string user, UserGroup group)
{
var name = new StringBuilder(512);
var nameSize = (uint)name.Capacity;
var refDomainName = new StringBuilder(512);
var refDomainNameSize = (uint)refDomainName.Capacity;
var sid = new IntPtr();
switch (group)
{
case UserGroup.PerformanceMonitorUsers:
ConvertStringSidToSid("S-1-5-32-558", out sid);
break;
case UserGroup.Administrators:
ConvertStringSidToSid("S-1-5-32-544", out sid);
break;
// Add additional Group/cases here.
}
// Find the user and populate our local variables.
SID_NAME_USE sidType;
if (!LookupAccountSid(null, sid, name, ref nameSize,
refDomainName, ref refDomainNameSize, out sidType))
return false;
LOCALGROUP_MEMBERS_INFO_3 info;
info.Domain = user;
// Add the user to the group.
var val = NetLocalGroupAddMembers(null, name.ToString(), 3, ref info, 1);
// If the user is in the group, success!
return val.Equals(SUCCESS) || val.Equals(ERROR_MEMBER_IN_ALIAS);
}
Hopefully this will be of interest to someone else, and I would still like to know if anyone comes across a working, fully managed solution.
I would like to be able to retrieve the SID of a local Machine like the PSGetSID
utility from Sysinternals but using C#.
Is this possible?
Edit:
I am looking for a solution that will work for computers that may or may not be members of a Domain.
This has good helper class to use lookupaccountname win32 api call.
get machine SID (including primary domain controller)
I did not find a way to do this with native C#
You could do it via pinvoke and just get it from the Win32 API.
http://www.pinvoke.net/default.aspx/advapi32.lookupaccountname
There may also be a way to get it in "pure" .NET.
Another SO post from user ewall has a pure .NET solution with examples in both C# and PowerShell. It uses the DirectoryEntry and SecurityDescriptor objects. See:
How can I retrieve a Windows Computer's SID using WMI?
SIDs documentation:
https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers
Local user account SID form: S-1-5-21-xxxxxxxxx-xxxxxxxxx-xxxxxxxxxx-yyyy
System specific part: xxx...xxx
User account specific part: yyyy
So you just need to remove the last group of digits from a user account SID to get the system SID.
If your code is a Windows application, you can get the system SID this way:
using System.Security.Principal;
string systemSid;
using (WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent())
{
systemSid = windowsIdentity.User.Value.Substring(0, windowsIdentity.User.Value.LastIndexOf('-'));
}
If your code is a web application it usually runs in the context of an application pool identity and if your code is a Windows service it usually runs in the context of a system account (system, local service, network service). None of these identities are user accounts. So you cannot use the code above. You need to either know a user account name, or list user accounts.
If you know a user account name, you can get the system SID this way:
using System.Security.Principal;
NTAccount ntAccount = new NTAccount("MACHINE_NAME\\UserAccountName");
SecurityIdentifier sid = (SecurityIdentifier)ntAccount.Translate(typeof(SecurityIdentifier));
string systemSid = sid.Value.Substring(0, sid.Value.LastIndexOf('-'));
But you cannot assume the name of a standard user like "guest" because standard local user accounts names are localized, because this standard user account may have been deleted, and because a standard user account may be suppressed in future Windows releases.
So most of the time, from a web application or from a Windows service, you need to list user accounts by means of WMI:
// your project must reference System.Management.dll
using System.Management;
SelectQuery selectQuery = new SelectQuery("SELECT * FROM Win32_UserAccount");
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(selectQuery);
string systemSid;
foreach (ManagementObject managementObject in managementObjectSearcher.Get())
{
if (1 == (byte)managementObject["SIDType"])
{
systemSid = managementObject["SID"] as string;
break;
}
}
systemSid = systemSid.Substring(0, systemSid.Value.LastIndexOf('-'));
Win32_UserAccount class documentation:
https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
UPDATE
About 100 times faster:
using Microsoft.Win32;
RegistryKey key = null;
string sid = null;
try
{
foreach (string subKeyName in Registry.Users.GetSubKeyNames())
{
if(subKeyName.StartsWith("S-1-5-21-"))
{
sid = subKeyName.Substring(0, subKeyName.LastIndexOf('-'));
break;
}
}
}
catch (Exception ex)
{
// ...
}
finally
{
if (key != null)
key.Close();
}
Within c#, I need to be able to
Connect to a remote system, specifying username/password as appropriate
List the members of a localgroup on that system
Fetch the results back to the executing computer
So for example I would connect to \SOMESYSTEM with appropriate creds, and fetch back a list of local administrators including SOMESYSTEM\Administrator, SOMESYSTEM\Bob, DOMAIN\AlanH, "DOMAIN\Domain Administrators".
I've tried this with system.directoryservices.accountmanagement but am running into problems with authentication. Sometimes I get:
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)
The above is trying because there will be situations where I simply cannot unmap existing drives or UNC connections.
Other times my program gets UNKNOWN ERROR and the security log on the remote system reports an error 675, code 0x19 which is KDC_ERR_PREAUTH_REQUIRED.
I need a simpler and less error prone way to do this!
davidg was on the right track, and I am crediting him with the answer.
But the WMI query necessary was a little less than straightfoward, since I needed not just a list of users for the whole machine, but the subset of users and groups, whether local or domain, that were members of the local Administrators group. For the record, that WMI query was:
SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = "Win32_Group.Domain='thehostname',Name='thegroupname'"
Here's the full code snippet:
public string GroupMembers(string targethost, string groupname, string targetusername, string targetpassword)
{
StringBuilder result = new StringBuilder();
try
{
ConnectionOptions Conn = new ConnectionOptions();
if (targethost != Environment.MachineName) //WMI errors if creds given for localhost
{
Conn.Username = targetusername; //can be null
Conn.Password = targetpassword; //can be null
}
Conn.Timeout = TimeSpan.FromSeconds(2);
ManagementScope scope = new ManagementScope("\\\\" + targethost + "\\root\\cimv2", Conn);
scope.Connect();
StringBuilder qs = new StringBuilder();
qs.Append("SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = \"Win32_Group.Domain='");
qs.Append(targethost);
qs.Append("',Name='");
qs.Append(groupname);
qs.AppendLine("'\"");
ObjectQuery query = new ObjectQuery(qs.ToString());
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
ManagementPath path = new ManagementPath(m["PartComponent"].ToString());
{
String[] names = path.RelativePath.Split(',');
result.Append(names[0].Substring(names[0].IndexOf("=") + 1).Replace("\"", " ").Trim() + "\\");
result.AppendLine(names[1].Substring(names[1].IndexOf("=") + 1).Replace("\"", " ").Trim());
}
}
return result.ToString();
}
catch (Exception e)
{
Console.WriteLine("Error. Message: " + e.Message);
return "fail";
}
}
So, if I invoke Groupmembers("Server1", "Administrators", "myusername", "mypassword"); I get a single string returned with:
SERVER1\Administrator
MYDOMAIN\Domain Admins
The actual WMI return is more like this:
\\SERVER1\root\cimv2:Win32_UserAccount.Domain="SERVER1",Name="Administrator"
... so as you can see, I had to do a little string manipulation to pretty it up.
This should be easy to do using WMI. Here you have a pointer to some docs:
WMI Documentation for Win32_UserAccount
Even if you have no previous experience with WMI, it should be quite easy to turn that VB Script code at the bottom of the page into some .NET code.
Hope this helped!
I would recommend using the Win32 API function NetLocalGroupGetMembers. It is much more straight forward than trying to figure out the crazy LDAP syntax, which is necessary for some of the other solutions recommended here. As long as you impersonate the user you want to run the check as by calling "LoginUser", you should not run into any security issues.
You can find sample code for doing the impersonation here.
If you need help figuring out how to call "NetLocalGroupGetMembers" from C#, I reccomend that you checkout Jared Parson's PInvoke assistant, which you can download from codeplex.
If you are running the code in an ASP.NET app running in IIS, and want to impersonate the user accessing the website in order to make the call, then you may need to grant "Trusted for Delegation" permission to the production web server.
If you are running on the desktop, then using the active user's security credentials should not be a problem.
It is possible that you network admin could have revoked access to the "Securable Object" for the particular machine you are trying to access. Unfortunately that access is necessary for all of the network management api functions to work. If that is the case, then you will need to grant access to the "Securable Object" for whatever users you want to execute as. With the default windows security settings all authenticated users should have access, however.
I hope this helps.
-Scott
You should be able to do this with System.DirectoryServices.DirectoryEntry. If you are having trouble running it remotely, maybe you could install something on the remote machines to give you your data via some sort of RPC, like remoting or a web service. But I think what you're trying should be possible remotely without getting too fancy.
If Windows won't let you connect through it's login mechanism, I think your only option is to run something on the remote machine with an open port (either directly or through remoting or a web service, as mentioned).