Creating a shared directory and setting security with C#

I’ve recently had to write an application to, among other things, create a shared directory in the file system and assign security privileges to specific users. In addition, I also needed to modify permissions of non-shared directories and ensure they propagated to its children. This set me on the path into calling WMI functions from .NET. These included Win32_Share, Win32_Trustee, Win32_Ace, Win32_SecurityDescriptor, and Win32_LogicalFileSecuritySetting. I’m not even going to pretend I completely understand what all the code is doing. But I figured I’d at least document what I got working from my hours of scouring the web.

abandonallhope
Let’s first address how to share a directory. When creating a share, you need to ensure the directory is not already shared using the same name. Windows will allow you to share the same physical directory multiple times with different share names, but if you try to create a share with a name that already exist, you’ll get an error. So first we need a way to check if that share name is already used.

 

public static bool IsDirectoryShared(string fullDirectoryPath, string shareName)
{
    bool isShared = false;
    
    var sharedFolders = new List();
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from win32_share");
    
    foreach (ManagementObject share in searcher.Get())
    {
        string type = share["Type"].ToString();
        
        if (type == "0") // 0 = DiskDrive, 1 = Print Queue, 2 = Device, 3 = IPH
        {
            if (share["Path"].ToString().Equals(fullDirectoryPath, StringComparison.OrdinalIgnoreCase))
            {
                if (!string.IsNullOrWhiteSpace(shareName) && share["Name"].ToString().Equals(shareName, StringComparison.OrdinalIgnoreCase))
                {
                    isShared = true;
                }
                
                isShared = true; 
            }
        }
    }
    
    return isShared;
}

 

IsDirectoryShared accepts the physical path and the share name to look up. It uses the ManagementObjectSearcher to query all shares returned by Win32_Share. There might be a way to enhance the query to return a more specific dataset but I haven’t tried yet. Instead, I loop through the results and check if the share is from the disk drive (Win32_Share gets other types of shares also). Then I check to see if the path and share name matches what I’m looking for. If they match, I know that physical directory is already shared with that name.

Let’s pretend IsDirectoryShared returns false. Good, now we can move forward with creating the share. To complicate things, it’s not enough to create a share. I also only want certain users to be able to access it. I should also mention that in my case, I’m only dealing with local account. No domains here.

 

public static void SetDirectoryShare(string fullDirectoryPath, string accountToAccess, string description, string shareName)
{
    // Share folder
    ManagementClass managementClass = new ManagementClass("Win32_Share");
    ManagementBaseObject inParams = managementClass.GetMethodParameters("Create");
    ManagementBaseObject outParams;
    inParams["Description"] = description;
    inParams["Name"] = shareName;
    inParams["Path"] = fullDirectoryPath;
    inParams["Type"] = 0x0;
    
    // Grant access
    NTAccount account = new NTAccount(accountToAccess);
    SecurityIdentifier sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
    byte[] sidArray = new byte[sid.BinaryLength];
    sid.GetBinaryForm(sidArray, 0);
    ManagementObject Trustee = new ManagementClass(new ManagementPath("Win32_Trustee"), null);
    Trustee["Name"] = accountToAccess;
    Trustee["SID"] = sidArray;
    ManagementObject AdminACE = new ManagementClass(new ManagementPath("Win32_Ace"), null);
    // 1179817 - read    //1245631 - modify     2032127 - full
    AdminACE["AccessMask"] = 1179817;
    AdminACE["AceFlags"] = 3;
    AdminACE["AceType"] = 0;
    AdminACE["Trustee"] = Trustee;
    ManagementObject SecurityDescriptor = new ManagementClass(new ManagementPath("Win32_SecurityDescriptor"), null);
    SecurityDescriptor["ControlFlags"] = 4;
    SecurityDescriptor["DACL"] = new object[] { AdminACE };
    
    inParams["Access"] = SecurityDescriptor;
    
    outParams = managementClass.InvokeMethod("Create", inParams, null);
    
    var result = (uint)(outParams.Properties["ReturnValue"].Value);
    if (result != 0)
    {
        // Error occurred creating share
        StringBuilder msg = new StringBuilder(String.Format("An error occurred sharing directory {0} with share name {1} with user {2}. ", fullDirectoryPath, shareName, accountToAccess));
        switch (result)
        {
            case 2:
            msg.Append("Access denied.");
            break;
            case 22:
            msg.Append("Duplicate share.");
            break;
            default:
            break;
        }
    }
}

 

I access the Win32_Share class again but this time use it through the ManagementClass. Then I prepare the use its Create method and pass it some parameters. Next I give access to the one user that’s passed into my method. This is where it took me a while to get things working and where things start to get gray for me. The world of Trustee’s, Ace, and Dacl and how they relate will require more research on my part, but at least I know this code works. The bitmask used for the AccessMask is important because that will determine the level of access. You can read up on it here: https://msdn.microsoft.com/en-us/library/aa394063(v=vs.85).aspx. I had a hard time figuring out exactly what bitmask to use so I used this tool (https://technet.microsoft.com/en-us/library/2006.05.scriptingguy.aspx) to analyze an exiting shared directory and got bitmask value from there. You should also pay attention to the AceFlags property as that is what set’s the inheritance level.

Cool! Now that we got all of that working, time to go home early right? Nope. The client now wants to be able to grant permission to an unshared folder. *rolls up sleeves* Let’s get started.

When we don’t have to create a share, granting privileges to a user turns out to be easier than imagined.

 

public static void SetDirectorySecurityModifyAccess(string fullPath, string accountToAccess)
{
    DirectoryInfo dInfo = new DirectoryInfo(fullPath);
    DirectorySecurity dSecurity = dInfo.GetAccessControl();
    dSecurity.AddAccessRule(new FileSystemAccessRule(accountToAccess, FileSystemRights.Modify, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
    dInfo.SetAccessControl(dSecurity);
}

 

Hmm, only 4 lines of code. Where’s the fun in that? Let’s see how to do it the manly way.

 

public static void SetDirectorySecurityModifyAccessManlyWay(string fullPath, string accountToAccess)
{
    ManagementObject lfs = new ManagementObject(@"Win32_LogicalFileSecuritySetting.Path='" + fullPath + "'");
    bool EnablePrivileges = lfs.Scope.Options.EnablePrivileges;
    lfs.Scope.Options.EnablePrivileges = true;
    
    // Get the security descriptor for this object
    ManagementBaseObject outP = lfs.InvokeMethod("GetSecurityDescriptor", null, null);

    if (((uint)(outP.Properties["ReturnValue"].Value)) == 0)
    {
        ManagementBaseObject descriptor = ((ManagementBaseObject)(outP.Properties["Descriptor"].Value));
        ManagementBaseObject[] daclObject = ((ManagementBaseObject[])(descriptor.Properties["Dacl"].Value));
        
        Array newDacl = Array.CreateInstance(typeof(ManagementBaseObject), 1);
        ManagementObject trustee = new ManagementClass(new ManagementPath("Win32_Trustee"), null);
        ManagementObject ace = new ManagementClass(new ManagementPath("Win32_Ace"), null);
        trustee.Properties["Name"].Value = accountToAccess;
        ace.Properties["AccessMask"].Value = 1245631;
        ace.Properties["AceFlags"].Value = 3;
        ace.Properties["AceType"].Value = 0;
        ace.Properties["Trustee"].Value = trustee;
        newDacl.SetValue(ace, 0);
        
        descriptor.Properties["Dacl"].Value = newDacl;
        ManagementBaseObject inParams = lfs.GetMethodParameters("SetSecurityDescriptor");
        inParams["Descriptor"] = descriptor;
        
        ManagementBaseObject outParams = lfs.InvokeMethod("SetSecurityDescriptor", inParams, null);
        var ret = ((uint)(outParams.Properties["ReturnValue"].Value));
        if (ret != 0)
        {
            throw new Exception("Failed setting security");
        }
    }
    
    lfs.Scope.Options.EnablePrivileges = EnablePrivileges;
}

 

Nice, back to the Win32 stuff. I think if you do it this way, an advantage is being able to set the permission at a more granular level using the AccessMask property and your desired bitmask.

Now it’s time to go home.

Advertisements

Tools of my trade

Every craftsman has their go-to set of tools. Developers are no different. Regardless of what framework or language you work in, experienced coders know the importance of tools. They can automate your work, make your work easier, or even do your work for you. Hey, I think it’s great if you use Notepad and the command prompt to compile your code. But don’t judge me if I rely on software to work smarter.

 

For reasons I don’t want to go into, I’ve been having to rebuild my machine multiple times recently. And every time, I need to remember what apps I had installed, download, and install them again. So to make it easier on myself, here’s the list…most if not all are free. I’m hoping to keep this list updated as new tools overtake others. But who am I kidding, that’s too much work. I wish there was a collective place to keep lists like this. Someone should create an app for that.

 

Anyways…

 

Visual Studio

https://www.visualstudio.com/downloads/

I do most of my work in the Microsoft stack. So Visual Studio is a must. I use VS Enterprise (cuz that’s how I roll) but there are two free versions. Community will do just fine for most software development. And if you’re on a Mac or a flavor of Linux, there’s VS Code.

 

 

VS Web Extension Pack

https://github.com/madskristensen/WebExtensionPack

If you do a lot of .NET web development like I do, you’ve probably heard of the Web Essentials extension for Visual Studio created by Mads Kristensen. Big fan of that guy…big fan! He put together a  pack of extensions that are pretty darn cool.

 

 

SQL Server Management Studio

https://msdn.microsoft.com/en-us/library/mt238290.aspx

Everyone loves a good SQL database. Why not be like the cool kids and connect to it with SSMS? The latest version (16.x.x as of this writing) improves the connection experience with  Azure SQL databases.

 

 

Remote Desktop Connection Manager

https://www.microsoft.com/en-us/download/details.aspx?id=44989

What? You’re still remoting onto servers with mstsc? Manage all your servers and credentials with RDCMan.

 

 

LINQPad

https://www.linqpad.net/

SQL queries are so 2007*. If you use Entity Framework as your ORM then you’re probably using LINQ to query your data. LINQPad allows you to test your LINQ queries and see what SQL is generated. Not only that, you can write full .NET apps and save them for later use. No solution or project files needed. This comes in handy if you have C# code that you want to execute every so often.

 

 

Fiddler

http://www.telerik.com/fiddler

A great tool to monitor traffic on your PC. There are other nice features like the ability to cache and manipulate a server’s response, simulate network traffic conditions, and manually construct HTTP requests.

 

 

Notepad++

https://notepad-plus-plus.org/download/v7.html

Like Notepad but 2 pluses better. Notepad++ has a multi-tab UI, auto formatting based on the file type, and a bunch of plugins allowing you to extend the functionality.

 

 

Slack

https://slack.com/

Not another collaboration tool, right? I like Slack over email, or Skype, or whatever because it actually facilitates collaboration among a group of people. Sure on the surface it looks like a chat room app, but when you factor in snippets, post, customizable extensions, and giphy, there’s real value there. Fun fact: Slack is an acronym for Searchable Log of All Conversations and Knowledge.

 

 

Azure Storage Explorer

https://azurestorageexplorer.codeplex.com/

If you have Azure storage, be it blob, table, or queue, you’ll need a way to access it. The version on codeplex is no longer actively maintained but it still works fine for me. There’s a new tool in preview – http://storageexplorer.com/

 

 

Sysinternal ShellRunas

https://technet.microsoft.com/en-us/sysinternals/shellrunas.aspx

A great little tool to run applications under a different user. The runas (netonly) is especially handy if you need to run it as a user of a different domain. Actually there are other great tools under the Windows Sysinternals umbrella.

 

 

f.lux

https://justgetflux.com/

Stop sitting in your dark cave, staring at your 20,000 lumen monitors. f.lux manages the intensity and hue of your monitor, automatically dimming your screens when the sun goes down. This adjustment is supposed to make your eyes less tired and even allow you to sleep better after a long night of hacking.

 

 

Browsers

Chrome & Firefox. Not much else to say here.

 

DebugView

https://technet.microsoft.com/en-us/sysinternals/debugview.aspx

Another one from the Sysinternal family. This nifty guy allows you view debug output from programs that are running on your machine. It’s useful if you’re writing an app and want to monitor it without having to attach a debugger.

 

 

Spotify

https://www.spotify.com

And of course you need tunes when you’re completely “plugged in”. Spotify is my player of choice. I used to use Pandora but you can’t beat creating your own playlist from an endless selection of music on Spotify.

 

 

 

*Kidding…sort of 😛

Saving a jQuery event handler for later use

Quick note: If you need to save an event handler so you can unbind the event and rebind it later, the code below will do the trick. This is especially helpful if you’re using a jQuery plugin that attaches events to your element, but you want to temporarily unbind it.

var submit = $("#submit");
//Save original click event handler for later
var click = submit.data("events")["click"][0].handler;
//Unbind click event
submit.unbind("click");
//Bind click to original event
submit.click(function (e) {
	$.proxy(click, submit)(e);
});

Stack Overflow is a sham

Let’s face it, if you’ve ever written a line of code in the past 3 years, you’ve more than likely landed on stackoverflow.com. It’s absolutely an indispensable weapon in a developer’s arsenal. But it’s all a guise. This no-nonsense, information-packed, almost fun site is only a ruse created by Jeff Atwood and Joel Spolsky. The real intent of the site is has nothing to do with technology, let alone programming. Continue reading “Stack Overflow is a sham”

RadChart with .NET MVC

I have a webform app that I converted to a webform/mvc hybrid. Everything was fine until I tested the Telerik RadCharts on the existing webform pages. It had worked before the conversion. Now whenever the chart was supposed to load, I was confronted with this error message:

Error loading RadChart image. You may also wish to check the ASP.NET Trace for further details. Display stack trace?

Clicking “OK” simply took me to a 404 page. I did notice the url of this was http://localhost:11239/reports/ChartImage.axd?UseSession=true&ChartID=139df24e-1578-44d3-988b-c66f2d7df98c_chart_ctl00$MainContent$ReportChart1&imageFormat=Png&random=0.725656411482793

Somehow MVC routing disrupted the RadChart handler. To fix this, you need to explicitly define the HttpHanderUrl property of the RadChart.

ReportChart1.HttpHandlerUrl = ResolveUrl("~/ChartImage.axd");

That should fix it!