Is there a .NET way of setting permissions on shared folders? All I've been able to find is suggestions to use XCACLS or SetACL
I would have thought there was a more .NET compliant method than that!
Printable View
Is there a .NET way of setting permissions on shared folders? All I've been able to find is suggestions to use XCACLS or SetACL
I would have thought there was a more .NET compliant method than that!
Do you mean share permissions or NTFS permissions? I know there are .NET methods for setting NTFS permissions (though I think they are quite complicated and hard to use successfully) but I'm not so sure about share permissions
Both actually. I need to create shared folders for users private data on a fileserver. So, I need to give them and them only access to the share and then set the NTFS permissions accordingly.
Have you tried: http://www.lmgtfy.com/?q=vb.net+set+folder+permissions ?
One useful link out of those results is this: http://msdn.microsoft.com/en-us/library/ms229742.aspx (an ACL is the proper name for the list of permissions that a folder has associated with it)
and this: http://msdn.microsoft.com/query/dev1...ng-VB)&rd=true
Thanks. I'd stumbled across the DirectorySecurity class over the weekend and got some working code to set the NTFS permissions. Now I'm just stuck on setting the share permissions.
This, it seems, is much harder ;)
Ah ok, well I just put an example of setting the NTFS permissions in the codebank here http://www.vbforums.com/showthread.php?p=3808272 but I'll take a look at the share permissions now as well..
So are you actually looking for how to share a folder (and set the share permissions in the process) or have you already done that bit and just need to set the share permissions on an existing shared folder?
I need to do the following
1) create a folder on a file server
2) Create a hidden share for that folder
3) Set the NTFS permissions so that the user has Change permissions, Helpdesk has Read-Only, Admins have Full Control
4) Set the SHARE permissions so that only the user can access the share
1-3 are done. Ideally, I would like to set the share permissions when creating the share (which is done with WMI and the win32_share namespace) but I've not been able to figure out how to do it yet.
Ah I see, well personally I dont like using WMI as its slow and I find it to be fairly unreliable, so I'm currently looking at using the NetShareAdd Win32 API to create the share and set the permissions but like you said, its not easy!
PS I'm not sure why you want users to be the only ones in the Share permissions list, the common way of configuring share permissions is to just set Everyone = Full Control and then use the NTFS permissions to actually lock the folder down as this makes it simpler and easier to manage. For example, your helpdesk wont be able to access the files without actually being on the server that the share is on and then browsing to the files locally, wouldnt it be better if they could access the files (as read only still) from the share so they dont have to connect to the server?
Another thread on ACL
http://www.vbforums.com/showthread.php?t=606932
That is just the NTFS permissions again, not share permissions
To be honest, I'm not sure why they do it like that either. In fact, all I'm doing is migrating a VB6 app to VB.NET so that I can add some functionality. Perhaps I'll just "adapt" the share creation procedure ;)
Creating these shares via the NetShareAdd API is proving to be damn tricky... well creating them isn't so bad, but creating them and setting the permissions is quite complicated. I'll try again tonight when I get home but giving up on it for now I'm afraid
Oh and here's the code I've got so far for creating a shared folder, in case you havent got that working yet. This works fine for just sharing the folder, but does not do anything with permissions:
vb Code:
'At the top of your code Imports System.Runtime.InteropServices 'API Definitions Const STYPE_DISKTREE As UInteger = 0 Public Enum NET_API_STATUS As Integer NERR_Success = 0 ERROR_ACCESS_DENIED = 5 ERROR_INVALID_PARAMETER = 87 ERROR_INVALID_NAME = 123 ERROR_INVALID_LEVEL = 124 NERR_UnknownDevDir = 2116 NERR_RedirectedPath = 2117 NERR_DuplicateShare = 2118 NERR_BufTooSmall = 2123 End Enum <StructLayoutAttribute(LayoutKind.Sequential)> _ Public Structure SHARE_INFO_2 '''LPWSTR->WCHAR* <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi2_netname As String Public shi2_type As UInteger '''LPWSTR->WCHAR* <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi2_remark As String Public shi2_permissions As UInteger Public shi2_max_uses As Integer Public shi2_current_uses As UInteger '''LPWSTR->WCHAR* <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi2_path As String '''LPWSTR->WCHAR* <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi2_passwd As String End Structure <DllImportAttribute("netapi32.dll", EntryPoint:="NetShareAdd")> _ Public Shared Function NetShareAdd(<InAttribute(), MarshalAsAttribute(UnmanagedType.LPWStr)> ByVal servername As String, ByVal level As UInteger, <InAttribute()> ByRef buf As SHARE_INFO_2, <OutAttribute()> ByRef parm_err As UInteger) As NET_API_STATUS End Function 'Example of using the APIs to create a share Private Sub CreateShare() Dim ShareInfo As New SHARE_INFO_2 With ShareInfo .shi2_netname = "TestShare" 'Set your share name here .shi2_type = STYPE_DISKTREE .shi2_remark = "Testing" 'Set share comment here .shi2_permissions = 0 .shi2_max_uses = -1 .shi2_current_uses = 0 .shi2_path = "C:\TestingFolder" 'Set share path here .shi2_passwd = Nothing End With Dim ParameterError As UInteger = 0 Dim Result As String = NetShareAdd(Nothing, 2, ShareInfo, ParameterError).ToString MessageBox.Show("Result = " & Result) End Sub
OK well I've spent literally the last 5 hours trying to get this working and I'm so close now but just keep getting a damn "Invalid Parameter" error on the last of about 6 API calls :( Its basically because MS decided not to provide any proper documentation on the internal members of the SECURITY_DESCRIPTOR structure... which is fine for C++ users as they can use the declaration of SECURITY_DESCRIPTOR directly from Windows, but us .NET people need to declare the type and all of its members correctly for it to work!
EDIT: I found the definition of the SECURITY_DESCRIPTOR structure by opening the Winnt.h C++ header file with Visual Studio :) unfortunately it doesnt help as its already pretty much what I was using :( so there must be something else I'm doing wrong, I just cant for the life of me figure out what it is and my code is now over 150 lines long... just to create one shared folder and set permissions on it
I admire your determination! I am slowly coming to terms with the fact that I may just resort to creating a process to run XCACLS.EXE and RMTSHARE.EXE to do it all.
Well I'm still working on it and I'll post the results here if I do get it working, here's what I've got currently for anyone else that wants to try and get it working or offer any suggestions :) all of the API calls work fine and seem to populate the relevant structures correctly and dont return any error codes, right up until the call to NetShareAdd at the end which returns ERROR_INVALID_PARAMETER and sets the parm_err argument to 501 (this is supposed to point to which member of the SHARE_INFO_502 structure is causing the invalid parameter error but there are only 10 members so 501 isnt much use!).
vb Code:
#Region "Constants" Const STYPE_DISKTREE As UInteger = 0 Const SECURITY_DESCRIPTOR_REVISION As UInteger = 1 Const ACL_REVISION As UInteger = 2 Const NO_INHERITANCE As UInteger = 0 Const ACCESS_READ As Integer = &H1 Const ACCESS_WRITE As Integer = &H2 Const ACCESS_CREATE As Integer = &H4 Const ACCESS_EXEC As Integer = &H8 Const ACCESS_DELETE As Integer = &H10 Const ACCESS_ATRIB As Integer = &H20 Const ACCESS_PERM As Integer = &H40 Const ACCESS_ALL As Integer = &H7F #End Region #Region "Enums" Public Enum NET_API_STATUS As Integer NERR_Success = 0 ERROR_ACCESS_DENIED = 5 ERROR_INVALID_PARAMETER = 87 ERROR_INVALID_NAME = 123 ERROR_INVALID_LEVEL = 124 NERR_UnknownDevDir = 2116 NERR_RedirectedPath = 2117 NERR_DuplicateShare = 2118 NERR_BufTooSmall = 2123 End Enum Public Enum ACCESS_MODE As UInteger NOT_USED_ACCESS = 0 GRANT_ACCESS = 1 SET_ACCESS = 2 DENY_ACCESS = 3 REVOKE_ACCESS = 4 SET_AUDIT_SUCCESS = 5 SET_AUDIT_FAILURE = 6 End Enum Public Enum MULTIPLE_TRUSTEE_OPERATION As UInteger NO_MULTIPLE_TRUSTEE = 0 TRUSTEE_IS_IMPERSONATE = 1 End Enum Public Enum TRUSTEE_FORM As UInteger TRUSTEE_IS_SID = 0 TRUSTEE_IS_NAME = 1 TRUSTEE_BAD_FORM = 2 TRUSTEE_IS_OBJECTS_AND_SID = 3 TRUSTEE_IS_OBJECTS_AND_NAME = 4 End Enum Public Enum TRUSTEE_TYPE As UInteger TRUSTEE_IS_UNKNOWN = 0 TRUSTEE_IS_USER = 1 TRUSTEE_IS_GROUP = 2 TRUSTEE_IS_DOMAIN = 3 TRUSTEE_IS_ALIAS = 4 TRUSTEE_IS_WELL_KNOWN_GROUP = 5 TRUSTEE_IS_DELETED = 6 TRUSTEE_IS_INVALID = 7 TRUSTEE_IS_COMPUTER = 8 End Enum #End Region #Region "Structures" <StructLayoutAttribute(LayoutKind.Sequential)> _ Public Structure SHARE_INFO_502 <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi502_netname As String Public shi502_type As UInteger <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi502_remark As String Public shi502_permissions As Integer Public shi502_max_uses As Integer Public shi502_current_uses As Integer <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi502_path As String <MarshalAsAttribute(UnmanagedType.LPWStr)> Public shi502_passwd As String Public shi502_reserved As Integer Public shi502_security_descriptor As SECURITY_DESCRIPTOR End Structure <StructLayoutAttribute(LayoutKind.Sequential)> _ Public Structure SECURITY_DESCRIPTOR Public Revision As Byte Public Sbz1 As Byte Public Control As UShort Public Owner As IntPtr Public Group As IntPtr Public Sacl As IntPtr Public Dacl As IntPtr End Structure <StructLayoutAttribute(LayoutKind.Sequential)> _ Public Structure ACL Public AclRevision As Byte Public Sbz1 As Byte Public AclSize As UShort Public AceCount As UShort Public Sbz2 As UShort End Structure <System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack:=0)> _ Public Structure EXPLICIT_ACCESS Public grfAccessPermissions As UInteger Public grfAccessMode As ACCESS_MODE Public grfInheritance As UInteger Public Trustee As TRUSTEE End Structure <System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack:=0)> _ Public Structure TRUSTEE Public pMultipleTrustee As UInteger Public MultipleTrusteeOperation As MULTIPLE_TRUSTEE_OPERATION Public TrusteeForm As TRUSTEE_FORM Public TrusteeType As TRUSTEE_TYPE <System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPTStr)> _ Public ptstrName As String End Structure #End Region #Region "Native Methods" <DllImportAttribute("netapi32.dll", EntryPoint:="NetShareAdd")> _ Public Shared Function NetShareAdd(<InAttribute(), MarshalAsAttribute(UnmanagedType.LPWStr)> ByVal servername As String, ByVal level As UInteger, <InAttribute()> ByRef buf As SHARE_INFO_502, <OutAttribute()> ByRef parm_err As Integer) As NET_API_STATUS End Function <System.Runtime.InteropServices.DllImportAttribute("advapi32.dll", EntryPoint:="InitializeSecurityDescriptor")> _ Public Shared Function InitializeSecurityDescriptor(ByRef pSecurityDescriptor As SECURITY_DESCRIPTOR, ByVal dwRevision As UInteger) As UInteger End Function <System.Runtime.InteropServices.DllImportAttribute("Advapi32.dll", EntryPoint:="SetEntriesInAclW")> _ Public Shared Function SetEntriesInAcl(ByVal cCountOfExplicitEntries As Integer, _ <System.Runtime.InteropServices.InAttribute()> ByRef pListOfExplicitEntries As EXPLICIT_ACCESS, <System.Runtime.InteropServices.InAttribute()> ByVal OldAcl As System.IntPtr, ByRef NewAcl As System.IntPtr) As UInteger End Function <System.Runtime.InteropServices.DllImportAttribute("Advapi32.dll", EntryPoint:="BuildExplicitAccessWithNameW")> _ Public Shared Sub BuildExplicitAccessWithName(ByRef pExplicitAccess As EXPLICIT_ACCESS, <InAttribute()> ByVal pTrusteeName As IntPtr, ByVal AccessPermissions As UInteger, ByVal AccessMode As UInteger, ByVal Inheritance As UInteger) End Sub <System.Runtime.InteropServices.DllImportAttribute("advapi32.dll", EntryPoint:="SetSecurityDescriptorDacl")> _ Public Shared Function SetSecurityDescriptorDacl(ByRef pSecurityDescriptor As SECURITY_DESCRIPTOR, <MarshalAsAttribute(UnmanagedType.Bool)> _ ByVal bDaclPresent As Boolean, <InAttribute()> ByVal pDacl As System.IntPtr, <MarshalAsAttribute(UnmanagedType.Bool)> ByVal bDaclDefaulted As Boolean) As UInteger End Function <DllImportAttribute("advapi32.dll", EntryPoint:="IsValidSecurityDescriptor")> _ Public Shared Function IsValidSecurityDesctiptor(ByRef pSecurityDescriptor As SECURITY_DESCRIPTOR) As UInteger End Function #End Region #Region "Managed Methods" Private Sub CreateShare(ByVal FullUsername As String) Dim ea As EXPLICIT_ACCESS = Nothing Dim AccountNamePtr As IntPtr = Marshal.StringToHGlobalUni(FullUsername) BuildExplicitAccessWithName(ea, AccountNamePtr, ACCESS_READ, ACCESS_MODE.SET_ACCESS, NO_INHERITANCE) Dim AclPtr As IntPtr Dim SetEntriesResult As UInteger = SetEntriesInAcl(1, ea, Nothing, AclPtr) MessageBox.Show("SetEntries = " & SetEntriesResult) Dim SecDesc As SECURITY_DESCRIPTOR Dim DecriptorInitResult As UInteger = InitializeSecurityDescriptor(SecDesc, SECURITY_DESCRIPTOR_REVISION) MessageBox.Show("InitSecurityDescriptor = " & DecriptorInitResult) Dim SetSecurityResult As UInteger = SetSecurityDescriptorDacl(SecDesc, True, AclPtr, False) MessageBox.Show("SetSecurityDescriptorDacl = " & SetSecurityResult) MessageBox.Show("Is Valid Descriptor = " & IsValidSecurityDesctiptor(SecDesc)) Dim ShareInfo As New SHARE_INFO_502 With ShareInfo .shi502_netname = "test" .shi502_type = STYPE_DISKTREE .shi502_remark = "Testing" .shi502_permissions = 0 .shi502_max_uses = -1 .shi502_current_uses = 0 .shi502_path = "C:\TestingFolder" .shi502_passwd = Nothing .shi502_reserved = 0 .shi502_security_descriptor = SecDesc End With 'Dim Dacl As ACL = DirectCast(Marshal.PtrToStructure(SecDesc.Dacl, GetType(ACL)), ACL) 'Dim ShareInfoSize As Integer = Marshal.SizeOf(ShareInfo) 'Dim SharePtr As IntPtr = Marshal.AllocCoTaskMem(ShareInfoSize) 'Marshal.StructureToPtr(ShareInfo, SharePtr, False) Dim ParameterError As Integer = 0 Dim Result As String = NetShareAdd(Nothing, 502, ShareInfo, ParameterError).ToString MessageBox.Show("NetShareAdd result = " & Result & ", param error = " & ParameterError) End Sub #End Region #Region "Event Handlers" Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click CreateShare(Environment.UserDomainName & "\" & Environment.UserName) End Sub #End Region
The problem definitely lies somewhere with the security descriptor because if I comment out this line then the code completes successfully and shares the folder (it just doesnt set the share permissions obviously) :
vb Code:
.shi502_security_descriptor = SecDesc
Nearly got it working :D Its setting the correct user for the share permissions now... just not actually giving them the permissions I requested. Getting there! :)
YAY I finally got it all working properly! :) Only taken 9 hours and 200 lines of code haha
Only caveat is that it doesnt work on a 64 bit OS if the program is set to target x64 - so it works on a 32 bit OS fine and works on a 64 bit OS as long as the program using it is set to target x86 not x64. Tested on Windows 7 and Windows XP and both worked perfectly - obviously the account running the code has to have permission to create shared folders but that goes without saying.
I'll post the full code in the codebank in a bit (and put a link here) when I've tidied it up and created a nice managed .NET method that wraps up the API functionality for others to use without having to understand the API :)
EDIT: OK I've got my .NET method pretty much finished now, and I've built a class to go with it that simplifies specifying users and their permissions (the SharePermissionEntry class that you see in the code below). I'll post that along with all of the API definitions tomorrow morning but basically once you have copied and pasted all of my API definitions and my .NET method then you can simply do this to create a shared folder:
vb Code:
'Create a list that will hold our permissions Dim PermissionsList As New List(Of SharePermissionEntry) 'Create a new permission entry for the Everyone group and allow them Read access Dim PermEveryone As New SharePermissionEntry(String.Empty, "Everyone", SharedFolder.SharePermissions.Read, True) 'Create a new permission entry for the currently logged on user and allow them Full Control Dim PermUser As New SharePermissionEntry(Environment.UserDomainName, Environment.UserName, SharedFolder.SharePermissions.FullControl, True) 'Add the two entries declared above to the permissions list PermissionsList.Add(PermUser) PermissionsList.Add(PermEveryone) 'Share the folder as "Test Share" and pass in the permissions list Dim Result As SharedFolder.NET_API_STATUS = _ SharedFolder.ShareExistingFolder("Test Share", "This is a test share", "C:\TestFolder", PermissionsList) If Result = SharedFolder.NET_API_STATUS.NERR_Success Then MessageBox.Show("Share created successfully!") End If
:)
Wow, you really spent that long on it? I thought I was obsessive about fixing problems ;)
I look forward to trying this out though!
This does seem like a rather strange thing for MS to have missed out of the .NET framework though. You would have imagined it's a fairly common thing to want to do in code.
Yeah well I spent a while trying to get it working on my own yesterday, then ended up searching google looking for a .NET example but could not find a single one anywhere on the internet that actually set the share permissions as well as creating the share... which made me more determined to get it working as it will probably help quite a few people out.
Well I dunno, in the grand scheme of things I dont think setting share permissions is that common compared to things like setting NTFS permissions or general file operations. At the end of the day MS had to have a deadline for the .NET framework release and I'm sure there is loads of stuff they would like to put in there but just didnt get chance as other things took priority.
PS If anyone does find a .NET example on the internet somewhere I would be interested to see it to see how similar their implementation is to mine :)
I'm still somewhat wary of writing unmanaged code. Not because it doesn't work but I'm still learning how to write good MANAGED code!
lol I just dont like calling windows APIs from .NET because they dont seem to follow any hard and fast rules - like in one API call that wants a String as an argument you will be able to just pass a regular .NET String, but in another you will have to get a pointer to that String and pass the pointer to the API instead, then in another you can pass a normal String as long as you decorate it with the correct Marshall attribute... etc etc. But then that makes it more of a challenge I guess :P
I'll post the full API code (and the managed code that wraps it) in a couple of minutes, just adding comments to it all :)
I guess this isn't the best time to ask if the code lets you pass alternative credentials to be used for creating the share :) :)
lol no there is no option to do that with the NetShareAdd API but I'm guessing you could use Impersonation in .NET before you call it - I've just never really tried to use impersonation myself so couldn't tell you exactly how.
Here we go :) all API definitions and my .NET method that wraps up the API functionality: http://www.vbforums.com/showthread.php?p=3809818
Just trying it now. First thing I notice is that the API does not appear to support UNC names for the folder path. I suppose I could map a drive to the share first....
Huh? I think you are getting something confused - you can only share a local folder. You cant share a UNC path because a UNC path is essentially what is created by a share... so you have to create a share before you even have a UNC path.
EDIT: If you are wanting to create the share on a remote machine then there is an option for that with the NetShareAdd API - I just havent tried to get it working yet as I thought you only wanted to share a folder on the machine the code would be running on
Hence why the current tool uses RMTSHARE.EXE I guess. I need to create multiple shares per user on our file servers. It wouldn't be practical to have to run the tool on the file servers themselves!
Why not? What difference does it make if you have to run a program on your PC or just remote desktop onto the file server and run a program?
Anyway - I've changed the code now so that it works on a remote machine :) just tested it against a 2008 server here and it worked perfectly. Will modify the codebank post in a minute.
The tool I'm updating is used for creating new users. It does multiple actions (setting password, creating mailboxes etc). One of things it needs to do is create several file shares. No-one is going to use it if it means logging on to each file server in turn and running the same tool over and over again when they can use the current tool once.
Of course, this begs the question....how does RMTShare do it?
Codebank post updated with the new class definition that lets you create remote shares as well :D http://www.vbforums.com/showthread.p...18#post3809818
Shall I take it from your rating of that last post that the code does what you wanted now? :)
Perfectly! Thank you ever so much. I shall have to sit down with it when I have a moment and understand it.
I am wondering if my fear of unmanaged code can be overcome. Presumably everything one needs to do the Enums and Structures are on MSDN and then it's just a matter of wrestling with it all to get it working.
Yeah to be honest that one was a lot harder than all other APIs I have worked with. Usually its just a case of looking up the function definition in the .NET API Viewer (click the link at the top of this article titled CLRInsideOut2008_01.exe http://msdn.microsoft.com/en-us/magazine/cc164193.aspx), copying and pasting that into your code, then doing the same for any structures/enums required. Once you have got all of the definitions, I usually have a go at getting it working myself first as some APIs are fairly straight forward, but often a quick google search gives you an idea of what you should be doing.
Some people recommend looking on www.pinvoke.net for definitions but if I'm being honest I dont think I have ever found a single example on there that actually just worked straight away... perhaps I have just been unlucky.