Using XmlUriResolver to provide credentials to remote share - c#

Thank you in advance for any and all help.
I have seen several post on Stack Overflow and other sites on the net (including Microsoft Docs), but none seeming to help resolve my issue.
I am working on a project that connects to a xml file on a remote file share from WinPE. I have used suggestions from another post on SO about using the XmlUriResolver class to "moderate" success.
The issue I am currently being faced with is either a "Access Denied" error (several attempts to modify the XmlUriResolver.Credentials class) or a complete failure without any reason for failure. I am assuming it is going to be another "Access Denied" error, but my try/catch (Exception e) isn't capturing the reason.
Below is a snippet and relevant part of the method I am trying to implement:
relativeSysData = relativeSysData.Replace(#"\", "/");
SysdataXml = $"file://{_globaldata.Server}/{_globaldata.Share}/{relativeSysData}";
XmlUrlResolver XmlResolver = new XmlUrlResolver();
XmlResolver.Credentials = new NetworkCredential(_UserName,
_Password);
XmlReaderSettings xmlSettings = new XmlReaderSettings();
xmlSettings.XmlResolver = XmlResolver;
LogWriter.WriteLogFile((int)LogWriter.EventID.Xml, (int)LogWriter.EventType.Info,
$"Connecting to (remote) System information store at: {SysdataXml}");
xDoc = XDocument.Load(XmlReader.Create(SysdataXml, xmlSettings));
if (xDoc == null)
{
LogWriter.WriteLogFile((int)LogWriter.EventID.XmlError, (int)LogWriter.EventType.Error,
$"Unable to connect to the (remote) data store located at: {SysdataXml}");
deploy = false;
capture = false;
return result;
}
This part of the method is specific to accessing the remote data. I have a try/catch statement encompassing all of the method.
If I have an open connection to the Network Share, the method will work flawlessly, but will cause the application this application starts to fail - due to an open connection already.
I have a possible alternative to work round this solution but will cost extra lines of code.
Any help in resolving this matter, without resorting to WNetAddConnection() will be gratefully received
Kind Regards
Richie
N.B. I am aware that this is an authentication issue.

After research into how the XmlUrlResolver class works, I think that it may be referencing/accessing API's that are not included within the WinPE API framework.
After testing, I have used the WNetAddConnection2() API to connect to the Server and authenticate before anything happens, ensuring that the local name of the NETRESOURCE structure is left null, and that DisplayType is set to Server.
public static int ConnectToServer()
{
int result = 0;
var _netresource = new NetResource()
{
scope = _ResourceScope.GlobalNetwork,
ResourceType = _ResourceType.Any,
DisplayType = _ResourceDisplayType.Server,
RemoteName = $#"\\{Program._globaldata.Server}"
};
string _username = $#"{Program._globaldata.Server}\RemoteDKBuild";
string _passsword = "Hic3nuasno6epyndtenars4yDifrts";
var _connectiontype = (int)_ConnectionType.Interactive | (int)_ConnectionType.Temporary;
LogWriter.WriteLogFile((int)LogWriter.EventID.NetMan, (int)LogWriter.EventType.Info,
$#"Attempting to connect to: \\{_netresource.RemoteName}");
result = WNetAddConnection2(_netresource, _passsword, _username, _connectiontype);
var res = Marshal.GetLastWin32Error();
if (result !=0)
{
LogWriter.WriteLogFile((int)LogWriter.EventID.NetMan, (int)LogWriter.EventType.Error,
$#"Failed to connect to: \\{_netresource.RemoteName}, Return Result: {result.ToString()}, Win32 Error Code: {res.ToString()}");
result = res;
}
else
{
LogWriter.WriteLogFile((int)LogWriter.EventID.NetMan, (int)LogWriter.EventType.Info,
$#"Connection to: \\{_netresource.RemoteName} has been successfull.");
}
return result;
}
[DllImport("mpr.dll", SetLastError = true)]
private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);

Related

C# SCCM - Client Action

I've seen many ways in powershell to force a computer to do a client action from the configuration manager.
Where I do work, it is not possible because we can't invoke commands on distant computer because it is blocked and the senior IT do not want to unlock it.
I did find a library in c# that allow me to do some action in sccm :
AdminUI.SmsTraceListener.dll
AdminUI.WqlQueryEngine.dll
I can add/remove computer to a collections, make queries and get the data, but I didn't find the way to force a computer to make an action from the configuration manager.
Is there someone here that knows if it is possible and how?
Thanks.
Edit 1: While searching in the MSDN documentation, I did find the TriggerSchedule Method in Class SMS_Client but I don't find the way to use it correctly. I think it might be the way to go, but i'm still stuck on this.
It is possible to trigger an Machine Policy Update via TriggerSchedule like this
ManagementScope scope = new ManagementScope(#"\\.\root\ccm");
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("TriggerSchedule");
inParams["sScheduleID"] = "{00000000-0000-0000-0000-000000000021}";
ManagementBaseObject outMPParams = cls.InvokeMethod("TriggerSchedule", inParams, null);
You already found the other Parameters for the sScheduleID in the link you posted. This uses standard WMI. With WqlQueryEngine you would get access to some WMI wrappers that can basically do the same thing. I do not see many advantages however.
Using the scope like this
\\.\root\ccm
makes the whole thing only work locally which is what you want if I understood you correctly. Otherwise replacing the . With a hostname or IP would make it work remotely. Only thing I found a bit strange is that it needs administrative rights, which should in theory not be necessary for a policy update request.
if someone is having the issue that nothing is happening, it is because WMI required higher rights. To leave triggering the actions also by the user, I switched to use the CPApplet:
TriggerSccmActions("Request & Evaluate", true);
private static List<string> TriggerSccmActions(string stringActions, bool boolContains)
{
List<string> actionName = new List<string>();
try {
const string ProgID = "CPApplet.CPAppletMgr";
Type foo = Type.GetTypeFromProgID(ProgID);
dynamic COMobject = Activator.CreateInstance(foo);
var oClientActions = COMobject.GetClientActions;
foreach (var oClientAction in oClientActions)
{
if (oClientAction.Name.ToString().Contains(stringActions) && boolContains)
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
else if (!(oClientAction.Name.ToString().Contains(stringActions)) && !(boolContains))
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
}
} catch(Exception e)
{
actionName.Add("Error: " + e.Message.ToString());
}
return actionName;
}
For me, EvaluateMachinePolicy Method in Class SMS_Client class worked. Here is the code:
public static void RefreshMachinePolicy(string machineName)
{
ManagementScope scope = new ManagementScope(string.Format(#"\\{0}\root\ccm", machineName));
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("EvaluateMachinePolicy");
ManagementBaseObject outMPParams = cls.InvokeMethod("EvaluateMachinePolicy", inParams, null);
Console.WriteLine("Policy refreshed successfully by EvaluateMachinePolicy method");
}
Here is the MSDN link for method details. Please include below namespace at the top of your source code file:
using System.Management;

C#.NET implementation of checkdnsrr() function in php

I am writing a desktop application which requires the user's email id to be validated.
Can someone please tell me the c#.net implementation of the http://php.net/manual/en/function.checkdnsrr.php'>checkdnsrr() function in php and if it doesn't exist, give me some pointers to write my own implementation of it? I've searched the web for hours to get the answer but couldn't find one.
Thanks in advance.
Edit(s):
Please note that this question is NOT a duplicate of How to check if an email address exists without sending an email?
There really is no bulletproof way of doing this which can be relied upon, even though it has existed for years within the FINGER protocol; which for privacy/security/spam sake is disabled on most systems.
So that leaves us with several standard methods to verify, none of which is perfect. The most common ones I have seen in .NET have been of these varieties:
Regular Expressions: A variety of patterns are available to see if the pattern matches what the RFC says is a legitimate address.
System.Net.Mail Usage of the MailAddress class to see if a designated input can be a valid address. This actually does a pretty good job; however, it will allow items through that we would not consider valid addresses, such as no top level domain.
private bool isValidEmail (string emailAddress) {
bool ReturnValue;
try {
MailAddress ma = new MailAddress(emailAddress);
ReturnValue = true;
}
catch (Exception) {
ReturnValue = false;
}
return ReturnValue;
}
But what you are looking for will take a little more it seems. What I am reading is that you want to verify the host exists, and check to see if it can accept mail. I know of know pretty way to do that, but you can use a command shell to run nslookup with a type of MX to see if you get an acceptable return. The following code sample is incomplete in this regard, you will have to create a parsing function and develop to your own standard.
protected static bool DnsLookup(string HostName) {
ProcessStartInfo ProcInfo = new ProcessStartInfo("cmd");
ProcInfo.UseShellExecute = false;
ProcInfo.RedirectStandardInput = true;
ProcInfo.RedirectStandardOutput = true;
ProcInfo.CreateNoWindow = true;
bool ReturnValue;
using (Process process__1 = Process.Start(ProcInfo)) {
StreamWriter sw = process__1.StandardInput;
StreamReader sr = process__1.StandardOutput;
sw.WriteLine("nslookup -type=mx " + HostName);
sw.Close();
string Result = sr.ReadToEnd();
// Parse the Result string to determine validity
// and set ReturnValue
}
return ReturnValue;
}
What I would do would probably be to use a combination of the above. Use the MailAddress class to see if the format is valid, and then try to do the nslookup to see if the domain exists and has a mail server.
One caveat to the MailAddress class is that it parse the addresses into the local and domain portions, and appears to have an internal trim on the local portion; it will allow a space immediately before the # symbol; I generally do a space-check prior to implementation.

DirectorySearcher returns ERROR_MORE_DATA

I have developed an application that talks to the Directory Server and gets user information.
This application is a generic one and can talk to Active Directory or Any other Directory Services.
In one case where i use this application to read data from Radiant One VDS, the application fails with the ERROR_MORE_DATA. Following is the code that returns this error:
try
{
using (DirectoryEntry de = new DirectoryEntry("LDAP://" + server + "/" + basedn, username, pwd,AuthenticationTypes.None))
{
using (DirectorySearcher Searcher = new DirectorySearcher(de))
{
Searcher.Filter = "(&(objectClass=user))";
Searcher.ReferralChasing = ReferralChasingOption.All;
Searcher.PropertiesToLoad.Add("cn");
Searcher.PropertiesToLoad.Add("memberof");
Searcher.PageSize = 1000;
using (SearchResultCollection allUsers = Searcher.FindAll())
{
foreach (SearchResult user in allGroups)
{
.
.
.
.
}
}
}
}
}
catch(System.Exception ex)
{
}
In the above code, Searcher.FindAll() returns ERROR_MORE_DATA. When i searched i found the this article.
But, this article talks about .NET 1.0 and my application runs with .NET 3.5
Can you anyone please help me here? Is there any way to fix this without going for DirectoryServices.Protocols?
Generally, this issue comes in the following situation:-
If one or more entries are found in Network and the buffer size is not enough to hold it.
see ERROR_MORE_DATA
You just need to specify the size of buffer.
see: specify the size of buffer in network call

Cannot get LDAP connection working - always "Unknown error"

I'm researching what I need to build an Active Directory search tool which ultimately will be used in a C# ASP.NET web app.
I know very little about AD (and don't particularly want to know any more than necessary) so I've asked our tech ops to set up a dummy instance on a server. This they've done giving it the domain dummy.local
I now need to work out my LDAP connection string. Here I'm completely stuck. I'm using a user account which is a member of Domain Admins. After loads of hunting round the web I've tried all sorts of things to work out the various components of the LDAP connection string. For example, if I run the following in cmd.exe for that domain admin user on the server...
dsquery user -samid "username" | dsget user -memberof -expand
...this gives me the following information:
"CN=Domain Admins,CN=Users,DC=dummy,DC=local"
"CN=Remote Desktop Users,CN=Builtin,DC=dummy,DC=local"
"CN=Users,CN=Builtin,DC=dummy,DC=local"
"CN=Administrators,CN=Builtin,DC=dummy,DC=local"
"CN=Domain Users,CN=Users,DC=dummy,DC=local"
"CN=Denied RODC Password Replication Group,CN=Users,DC=dummy,DC=local"
I've also run the following in a C# console app...
using (var context = new PrincipalContext(ContextType.Domain))
using (var comp = ComputerPrincipal.FindByIdentity(context, Environment.MachineName))
{
Console.WriteLine(String.Join(",", comp.DistinguishedName.Split(',').SkipWhile(s => !s.StartsWith("OU=")).ToArray()));
}
...and this gives me the following:
OU=Domain Controllers,DC=dummy,DC=local
I thought I therefore had all the properties I needed to build my LDAP connection string. I ended up with this:
LDAP://COMSECWEBDEV.dummy.local/ou=Domain Controllers,/cn=Domain Admins,/cn=Users,/dc=dummy,/dc=local
I've tried using this connection string, with the username and password of the domain admin which I know is correct, but everything I try always gives me the same error:
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
Since this error gives me no detail I have no idea what I'm doing wrong. I'm sure I'm just not getting the connection string right, but I've no idea how to work out the correct string.
For completeness, here is the console code which I'm testing with:
static void Main(string[] args)
{
var connString = ConfigurationSettings.AppSettings["lc"];
var username = ConfigurationSettings.AppSettings["lu"];
var password = ConfigurationSettings.AppSettings["lpw"];
using (DirectoryEntry de = new DirectoryEntry(connString, username, password))
{
DirectorySearcher search = new DirectorySearcher(de);
search.PageSize = 1001;// To Pull up more than 100 records.
DirectorySearcher directorySearcher = new DirectorySearcher(de);
directorySearcher.Filter = string.Format("(&(objectClass=user)(objectCategory=user) (sAMAccountName={0}))", username);
directorySearcher.PropertiesToLoad.Add("msRTCSIP-PrimaryUserAddress");
try
{
var result = directorySearcher.FindOne();
var found = false;
if (result != null)
{
if (result.Properties["msRTCSIP-PrimaryUserAddress"] != null)
{
found = true;
Console.WriteLine("Found: " + result.Properties["msRTCSIP-PrimaryUserAddress"][0]);
}
}
if (!found)
{
Console.WriteLine("Sad face");
}
}
catch (Exception x)
{
Console.WriteLine(x.Message);
}
Console.WriteLine("------------");
}
}
I was trying to figure out how to properly format a LDAP connection string last week, and found this entry over on serverfault:
How can I figure out my LDAP connection string?
I noticed you had a "/" between each OU or DC entry - I didn't include those in mine, and I don't see them included in the above example either.
I'm far from an expert (obviously) but just figured I would throw it out there.

dcomcnfg functionality programmatically

I can find all sorts of stuff on how to program for DCOM, but practically nothing on how to set/check the security programmatically.
I'm not trying to recreate dcomcnfg, but if I knew how to reproduce all the functionality of dcomcnfg in C# (preferred, or VB.net) then my goal is in sight.
I can't seem to be able to find any good resource on this, no open source API's or even quick examples of how to do each step. Even here DCOM or dcomcnfg returns few results and none really about how to set/verify/list security.
If anybody has some pointers to an open API or some examples I would appreciate it.
The answer posted by Daniel was HUGELY helpful. Thank you so much, Daniel!
An issue with Microsoft's documentation is that they indicate that the registry values contain an ACL in binary form. So, for instance, if you were trying to set the machine's default access (rather than per-process), you would be accessing registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\DefaultAccessPermission. However, in my initial attempts to access this key using the System.Security.AccessControl.RawACL class were failing.
As Daniel's code indicate's the value is not actually an ACL, but really is a SecurityDescriptor with the ACL in it.
So, even though I know this post is old, I'm going to post my solution for checking and setting the security settings and adding NetworkService for Default local access. Of course, you could take this and make it better I'm sure, but to get started you would simply need to change the key and the access mask.
static class ComACLRights{
public const int COM_RIGHTS_EXECUTE= 1;
public const int COM_RIGHTS_EXECUTE_LOCAL = 2;
public const int COM_RIGHTS_EXECUTE_REMOTE = 4;
public const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
public const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
}
class Program
{
static void Main(string[] args)
{
var value = Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", null);
RawSecurityDescriptor sd;
RawAcl acl;
if (value == null)
{
System.Console.WriteLine("Default Access Permission key has not been created yet");
sd = new RawSecurityDescriptor("");
}else{
sd = new RawSecurityDescriptor(value as byte[], 0);
}
acl = sd.DiscretionaryAcl;
bool found = false;
foreach (CommonAce ca in acl)
{
if (ca.SecurityIdentifier.IsWellKnown(WellKnownSidType.NetworkServiceSid))
{
//ensure local access is set
ca.AccessMask |= ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL; //set local access. Always set execute
found = true;
break;
}
}
if(!found){
//Network Service was not found. Add it to the ACL
SecurityIdentifier si = new SecurityIdentifier(
WellKnownSidType.NetworkServiceSid, null);
CommonAce ca = new CommonAce(
AceFlags.None,
AceQualifier.AccessAllowed,
ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL,
si,
false,
null);
acl.InsertAce(acl.Count, ca);
}
//re-set the ACL
sd.DiscretionaryAcl = acl;
byte[] binaryform = new byte[sd.BinaryLength];
sd.GetBinaryForm(binaryform, 0);
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", binaryform, RegistryValueKind.Binary);
}
}
Facing similar circumstances (configuring DCOM security from an MSI) I managed to create a solution that does what I want by altering registry key values in HKEY_CLASSES_ROOT\AppID{APP-GUID-GOES-HERE}. Thanks to Arnout's answer for setting me on the right path.
In specific, I created a method to edit the security permissions for DCOM objects, which are stored in the LaunchPermission and AccessPermission registry key values. These are serialized security descriptors, which you can access by passing the binary data through RawSecurityDescriptor. This class simplifies a lot of the details in a delicious .NET-y fashion, but you still have to attend to all the logical details regarding Windows ACL, and you have to make sure to write the security descriptor back to the registry by using RawSecurityDescriptor.GetBinaryForm.
The method I created was called EditOrCreateACE. This method will either edit an existing ACE for an account, or insert a new one, and ensure that the access mask has the passed flags set. I attach it here as an example, this is by no means any authority on how to deal with it, since I know very little of Windows ACL stuff still:
// These are constants for the access mask on LaunchPermission.
// I'm unsure of the exact constants for AccessPermission
private const int COM_RIGHTS_EXECUTE = 1;
private const int COM_RIGHTS_EXECUTE_LOCAL = 2;
private const int COM_RIGHTS_EXECUTE_REMOTE = 4;
private const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
private const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
void EditOrCreateACE(string keyname, string valuename,
string accountname, int mask)
{
// Get security descriptor from registry
byte[] keyval = (byte[]) Registry.GetValue(keyname, valuename,
new byte[] { });
RawSecurityDescriptor sd;
if (keyval.Length > 0) {
sd = new RawSecurityDescriptor(keyval, 0);
} else {
sd = InitializeEmptySecurityDescriptor();
}
RawAcl acl = sd.DiscretionaryAcl;
CommonAce accountACE = null;
// Look for the account in the ACL
int i = 0;
foreach (GenericAce ace in acl) {
if (ace.AceType == AceType.AccessAllowed) {
CommonAce c_ace = ace as CommonAce;
NTAccount account =
c_ace.SecurityIdentifier.Translate(typeof(NTAccount))
as NTAccount;
if (account.Value.Contains(accountname)) {
accountACE = c_ace;
}
i++;
}
}
// If no ACE found for the given account, insert a new one at the end
// of the ACL, otherwise just set the mask
if (accountACE == null) {
SecurityIdentifier ns_account =
(new NTAccount(accountname)).Translate(typeof(SecurityIdentifier))
as SecurityIdentifier;
CommonAce ns = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed,
mask, ns_account, false, null);
acl.InsertAce(acl.Count, ns);
} else {
accountACE.AccessMask |= mask;
}
// Write security descriptor back to registry
byte[] binarySd = new byte[sd.BinaryLength];
sd.GetBinaryForm(binarySd, 0);
Registry.SetValue(keyname, valuename, binarySd);
}
private static RawSecurityDescriptor InitializeEmptySecurityDescriptor()
{
var localSystem =
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var new_sd =
new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent,
localSystem, localSystem, null,
new RawAcl(GenericAcl.AclRevision, 1));
return new_sd;
}
Do note that this code is by no means perfect. If the entire registry key value for these ACL is missing in the registry, the ACL that is synthesized will ONLY grant access to the passed account and nothing else. I'm also sure there are lots of error conditions I've not handled properly and details I've glossed over. Again, it's an example of how to deal with DCOM ACL in .NET.
This information is stored in HKCR\AppID\{Your-AppID}\LaunchPermission and AccessPermission. These are REG_BINARY values containing serialized security descriptors. No idea whether there's anything providing convenient access to those from .NET...
More info on MSDN.
I couldn't find any .NET way of doing this - you can use the MS command line utility DCOMPerm (also here) which is part of the SDK.
I found this solution working:
public static void SetUp()
{
SetCOMSercurityAccess("DefaultAccessPermission");
SetCOMSercurityAccess("DefaultLaunchPermission");
}
private static void SetCOMSercurityAccess(string regKey)
{
//This is the magic permission!
byte[] binaryform = new string[]
{
"01","00","04","80","80","00","00","00","90","00","00","00","00","00","00","00","14","00","00","00","02","00","6c","00","04",
"00","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","12","00","00","00","00","00",
"24","00","0b","00","00","00","01","05","00","00","00","00","00","05","15","00","00","00","a3","53","d8","c8","94","bd","63",
"84","88","bf","fa","cf","a7","2b","00","00","00","00","18","00","1f","00","00","00","01","02","00","00","00","00","00","05",
"20","00","00","00","20","02","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","04",
"00","00","00","01","02","00","00","00","00","00","05","20","00","00","00","20","02","00","00","01","02","00","00","00","00",
"00","05","20","00","00","00","20","02","00","00"
}.Select(o=> Convert.ToByte(o,16)).ToArray();
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", regKey, binaryform, RegistryValueKind.Binary);
}
In case it help others...

Categories