Created
April 27, 2018 20:36
-
-
Save Bill-Stewart/f8239e22a119c6451337df9d33bb7b17 to your computer and use it in GitHub Desktop.
Windows PowerShell module for managing access-based enumeration for shares
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# AccessBasedEnumeration.psm1 | |
# Written by Bill Stewart | |
# | |
# This Windows PowerShell module manages access-based enumeration for shares. | |
# | |
# Access-based enumeration causes the operating system to display only file | |
# system items that a user has permission to access. If a user does not have | |
# read (or equivalent) permissions for an item, the operating system hides the | |
# item from the user's view. Access-based enumeration is active only when | |
# viewing file system items from a share; it does not affect a user's view of | |
# file system items in the local file system. | |
# | |
# This module is obsolete if you have the SmbShare module, but you can use it | |
# with older OS versions if needed. | |
#requires -version 2 | |
# Structs: | |
# * [Win32Api.NetApi32+SHARE_INFO_0] | |
# * [Win32Api.NetApi32+SHARE_INFO_1] | |
# * [Win32Api.NetApi32+SHARE_INFO_1005] | |
# Methods: | |
# * [Win32Api.NetApi32]::NetApiBufferFree() | |
# * [Win32Api.NetApi32]::NetShareEnum() | |
# * [Win32Api.NetApi32]::NetShareGetInfo() | |
# * [Win32Api.NetApi32]::NetShareSetInfo() | |
Add-Type -MemberDefinition @" | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
public struct SHARE_INFO_0 { | |
[MarshalAs(UnmanagedType.LPWStr)] public string shi0_netname; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
public struct SHARE_INFO_1 { | |
[MarshalAs(UnmanagedType.LPWStr)] public string shi1_netname; | |
public uint shi1_type; | |
[MarshalAs(UnmanagedType.LPWStr)] public string shi1_remark; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
public struct SHARE_INFO_1005 { | |
public uint shi1005_flags; | |
} | |
[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
public static extern uint NetApiBufferFree(IntPtr Buffer); | |
[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
public static extern int NetShareEnum( | |
[MarshalAs(UnmanagedType.LPWStr)] string servername, | |
uint level, | |
ref IntPtr bufptr, | |
uint prefmaxlen, | |
ref uint entriesread, | |
ref uint totalentries, | |
ref uint resume_handle); | |
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
public static extern int NetShareGetInfo( | |
[MarshalAs(UnmanagedType.LPWStr)] string servername, | |
[MarshalAs(UnmanagedType.LPWStr)] string netname, | |
uint level, | |
out IntPtr bufPtr); | |
[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
public static extern int NetShareSetInfo( | |
[MarshalAs(UnmanagedType.LPWStr)] string servername, | |
[MarshalAs(UnmanagedType.LPWStr)] string netname, | |
uint level, | |
IntPtr buf, | |
out uint parm_err); | |
"@ -Namespace Win32Api -Name NetApi32 | |
# Enum: | |
# * [AccessBasedEnumeration]::Disabled | |
# * [AccessBasedEnumeration]::Enabled | |
Add-Type -TypeDefinition @" | |
public enum AccessBasedEnumeration { | |
Disabled, | |
Enabled | |
} | |
"@ | |
$MAX_PREFERRED_LENGTH = [BitConverter]::ToUInt32([BitConverter]::GetBytes(-1), 0) | |
$STYPE_DISKTREE = 0x00000000 | |
$STYPE_SPECIAL = 0x80000000 | |
$STYPE_MASK = 0x000000FF | |
$SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM = 0x00000800 | |
# Writes a custom error record to the error stream | |
function Write-CustomError { | |
param( | |
[Exception] $exception, | |
$targetObject, | |
[String] $errorID, | |
[Management.Automation.ErrorCategory] $errorCategory = "NotSpecified" | |
) | |
$errorRecord = New-Object Management.Automation.ErrorRecord $exception,$errorID,$errorCategory,$targetObject | |
$PSCmdlet.WriteError($errorRecord) | |
} | |
# Outputs a SHARE_INFO_<level> struct for a share if it exists; outputs nothing | |
# and writes non-terminating error to error stream on failure | |
function NetShareGetInfo { | |
param( | |
$computerName, | |
$shareName, | |
$level | |
) | |
$pBuffer = [IntPtr]::Zero | |
$apiResult = [Win32Api.NetApi32]::NetShareGetInfo( | |
$computerName, # servername | |
$shareName, # netname | |
$level, # level | |
[Ref] $pBuffer # bufptr | |
) | |
if ( $apiResult -eq 0 ) { | |
$shareInfo = [Runtime.InteropServices.Marshal]::PtrToStructure($pBuffer, [Type] ("Win32Api.NetApi32+SHARE_INFO_$level" -as [Type])) | |
$shareInfo | |
[Void] [Win32Api.NetApi32]::NetApiBufferFree($pBuffer) | |
} | |
else { | |
$errorID = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name | |
$exception = New-Object ComponentModel.Win32Exception $apiResult | |
Write-CustomError $exception "\\$computerName\$shareName" $errorID | |
} | |
} | |
# Outputs a share's name if it exists | |
function NetShareGetName { | |
param( | |
$computerName, | |
$shareName | |
) | |
$shareInfo0 = NetShareGetInfo $computerName $shareName 0 | |
if ( $shareInfo0 ) { | |
$shareInfo0.shi0_netname | |
} | |
} | |
# Enumerates and outputs disk shares with access-based enumeration mode | |
function GetAccessBasedEnumeration { | |
param( | |
$computerName, | |
$shareName = "*" | |
) | |
$pBuffer = [IntPtr]::Zero | |
$entriesRead = $totalEntries = $resumeHandle = 0 | |
$apiResult = [Win32Api.NetApi32]::NetShareEnum( | |
$computerName, # servername | |
1, # level | |
[Ref] $pBuffer, # bufptr | |
$MAX_PREFERRED_LENGTH, # prefmaxlen | |
[Ref] $entriesRead, # entriesread | |
[Ref] $totalEntries, # totalentries | |
[Ref] $resumeHandle # resumehandle | |
) | |
if ( $apiResult -eq 0 ) { | |
$offset = $pBuffer.ToInt64() | |
for ( $i = 0; $i -lt $entriesRead; $i++ ) { | |
$pEntry = New-Object IntPtr($offset) | |
# Copy unmanaged buffer to managed variable | |
$shareInfo1 = [Runtime.InteropServices.Marshal]::PtrToStructure($pEntry, [Type] [Win32Api.NetApi32+SHARE_INFO_1]) | |
# Enumerate disk shares (but not admin shares) with matching names | |
if ( (($shareInfo1.shi1_type -band $STYPE_MASK) -eq $STYPE_DISKTREE) -and (($shareInfo1.shi1_type -band $STYPE_SPECIAL) -eq 0) -and ($shareInfo1.shi1_netname -like $shareName) ) { | |
$shareInfo1005 = NetShareGetInfo $computerName $shareInfo1.shi1_netname 1005 | |
if ( $shareInfo1005 ) { | |
if ( ($shareInfo1005.shi1005_flags -band $SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM) -ne 0 ) { | |
# Access-based enumeration is enabled if bit is set | |
$accessBasedEnumeration = [AccessBasedEnumeration]::Enabled | |
} | |
else { | |
# Access-based enumeration is disabled if bit is not set | |
$accessBasedEnumeration = [AccessBasedEnumeration]::Disabled | |
} | |
# Create output object | |
$outputObject = "" | Select-Object ` | |
@{Name = "ComputerName"; Expression = {$computerName}}, | |
@{Name = "ShareName"; Expression = {$shareInfo1.shi1_netname}}, | |
@{Name = "AccessBasedEnumeration"; Expression = {$accessBasedEnumeration}} | |
# Add extra properties if -Verbose | |
if ( $VerbosePreference -eq [Management.Automation.ActionPreference]::Continue ) { | |
$outputObject | Add-Member NoteProperty "shi1_type" ("0x{0:X8}" -f $shareInfo1.shi1_type) | |
$outputObject | Add-Member NoteProperty "shi1005_flags" ("0x{0:X8}" -f $shareInfo1005.shi1005_flags) | |
} | |
$outputObject | |
} | |
} | |
$offset += [Runtime.InteropServices.Marshal]::SizeOf($shareInfo1) | |
} | |
# Free unmanaged buffer | |
[Void] [Win32Api.NetApi32]::NetApiBufferFree($pBuffer) | |
} | |
else { | |
$errorID = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name | |
$exception = New-Object ComponentModel.Win32Exception $apiResult | |
Write-CustomError $exception "\\$computerName\$shareName" $errorID | |
} | |
} | |
<# | |
.SYNOPSIS | |
Gets the access-based enumeration mode of network shares from one or more computers. | |
.DESCRIPTION | |
Gets the access-based enumeration mode of network shares from one or more computers. Access-based enumeration causes the operating system to display only file system items that a user has permission to access. If a user does not have read (or equivalent) permissions for an item, the operating system hides the item from the user's view. Access-based enumeration is active only when viewing file system items from a share; it does not affect a user's view of file system items in the local file system. | |
.PARAMETER ShareName | |
Specifies one or more share names. This parameter supports wildcards. The default is all shares ("*"). | |
.PARAMETER ComputerName | |
Specifies one or more computer names. This parameter does not support wildcards. | |
.OUTPUTS | |
Objects containing the following properties: | |
* ComputerName - Name of computer | |
* ShareName - name of share | |
* AccessBasedEnumeration - Enabled or Disabled | |
.NOTES | |
The -Verbose parameter adds the shi1_type and shi1005_flags properties to output objects for diagnostic purposes. | |
.EXAMPLE | |
PS C:\> Get-AccessBasedEnumeration | |
Outputs the access-based enumeration mode for all shares on the current computer. | |
.EXAMPLE | |
PS C:\> Get-AccessBasedEnumeration -ComputerName SERVER1 | Where-Object { $_.AccessBasedEnumeration } | |
Outputs shares on the computer SERVER1 that have access-based enumeration enabled. | |
.LINK | |
Access-based Enumeration - https://technet.microsoft.com/en-us/library/dd772681.aspx | |
#> | |
function Get-AccessBasedEnumeration { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position = 0)] | |
[String[]] $ShareName = "*", | |
[Parameter(ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] | |
[String[]] $ComputerName = [Net.Dns]::GetHostName() | |
) | |
process { | |
foreach ( $computerNameItem in $ComputerName ) { | |
foreach ( $shareNameItem in $shareName ) { | |
if ( $shareNameItem -notmatch '\*' ) { | |
$netShareName = NetShareGetName $computerNameItem $shareNameItem | |
} | |
else { | |
$netShareName = $shareNameItem | |
} | |
if ( $netShareName ) { | |
GetAccessBasedEnumeration $computerNameItem $netShareName | |
} | |
} | |
} | |
} | |
} | |
function SetAccessBasedEnumeration { | |
param( | |
$computerName, | |
$shareName, | |
[AccessBasedEnumeration] $accessBasedEnumeration | |
) | |
$shareInfo1005 = NetShareGetInfo $computerName $shareName 1005 | |
if ( $shareInfo1005 ) { | |
if ( $accessBasedEnumeration -eq [AccessBasedEnumeration]::Enabled ) { | |
# Enabled = set bit | |
$shareInfo1005.shi1005_flags = $shareInfo1005.shi1005_flags -bor $SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM | |
} | |
else { | |
# Disabled = clear bit | |
$shareInfo1005.shi1005_flags = $shareInfo1005.shi1005_flags -band (-bnot $SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM) | |
} | |
# Allocate unmanaged buffer for struct | |
$pShareInfo1005 = [Runtime.InteropServices.Marshal]::AllocHGlobal([Runtime.InteropServices.Marshal]::SizeOf($shareInfo1005)) | |
# Copy struct to unmanaged buffer | |
[Runtime.InteropServices.Marshal]::StructureToPtr($shareInfo1005, $pShareInfo1005, $false) | |
$parmErr = 0 | |
$apiResult = [Win32Api.NetApi32]::NetShareSetInfo( | |
$computerName, # servername | |
$shareName, # netname | |
1005, # level | |
$pShareInfo1005, # buf | |
[Ref] $parmErr # parm_err | |
) | |
# Free unmanaged buffer | |
[Runtime.InteropServices.Marshal]::FreeHGlobal($pShareInfo1005) | |
if ( $apiResult -ne 0 ) { | |
$errorID = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name | |
$exception = New-Object ComponentModel.Win32Exception $apiResult | |
Write-CustomError $exception "\\$computerName\$shareName" $errorID | |
} | |
} | |
} | |
<# | |
.SYNOPSIS | |
Enables or disables access-based enumeration mode for one or more network shares on one or more computers. | |
.DESCRIPTION | |
Enables or disables access-based enumeration mode for one or more network shares on one or more computers. Access-based enumeration causes the operating system to display only file system items that a user has permission to access. If a user does not have read (or equivalent) permissions for an item, the operating system hides the item from the user's view. Access-based enumeration is active only when viewing file system items from a share; it does not affect a user's view of file system items in the local file system. | |
.PARAMETER AccessBasedEnumeration | |
Specifies whether to enable or disable access-based enumeration. | |
.PARAMETER ShareName | |
Specifies one or more share names. This parameter does not support wildcards. | |
.PARAMETER ComputerName | |
Specifies one or more computer names. This parameter does not support wildcards. | |
.NOTES | |
Changing access-based enumeration is a high-impact change. To bypass confirmation, you must specify -Confirm:$false. | |
.EXAMPLE | |
PS C:\> Set-AccessBasedEnumeration Enabled Apps | |
Enables access-based enumeration for the Apps share on the current computer. | |
.EXAMPLE | |
PS C:\> Set-AccessBasedEnumeration Disabled SharedFiles | |
Disables access-based enumeration for the SharedFiles share on the current computer. | |
.EXAMPLE | |
PS C:\> Get-AccessBasedEnumeration -ComputerName SERVER1 | Where-Object { -not $_.AccessBasedEnumeration } | Set-AccessBasedEnumeration Enabled -Confirm:$false | |
Enables access-based enumeration for all shares on SERVER1 that do not currently have it enabled. | |
.EXAMPLE | |
PS C:\> Get-Content Servers.txt | Get-AccessBasedEnumeration | Set-AccessBasedEnumeration Disabled | |
Disables access-based enumeration for all shares for all servers in the file Servers.txt. | |
.LINK | |
Access-based Enumeration - https://technet.microsoft.com/en-us/library/dd772681.aspx | |
#> | |
function Set-AccessBasedEnumeration { | |
[CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] | |
param( | |
[Parameter(Position = 0,Mandatory = $true)] | |
[AccessBasedEnumeration] $AccessBasedEnumeration, | |
[Parameter(Position = 1,Mandatory = $true,ValueFromPipelineByPropertyName = $true)] | |
[String[]] $ShareName, | |
[Parameter(ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] | |
[String[]] $ComputerName = [Net.Dns]::GetHostName() | |
) | |
process { | |
foreach ( $computerNameItem in $ComputerName ) { | |
foreach ( $shareNameItem in $ShareName ) { | |
$netShareName = NetShareGetName $computerNameItem $shareNameItem | |
if ( $netShareName ) { | |
if ( $PSCmdlet.ShouldProcess("\\$computerNameItem\$netShareName", "Set access-based enumeration to '$AccessBasedEnumeration'") ) { | |
SetAccessBasedEnumeration $computerNameItem $netShareName $AccessBasedEnumeration | |
} | |
} | |
} | |
} | |
} | |
} | |
Export-ModuleMember Get-AccessBasedEnumeration | |
Export-ModuleMember Set-AccessBasedEnumeration |
It's not really trivial; you would need to create a security descriptor and use it with the shi502_security_descriptor
member of the SHARE_INFO_502
structure. In any case, modern security practice suggests to use Everyone:Full Control
for the share permission itself, and secure everything at the file system level.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying to write a PowerShell script that can create SMB Shares using the Win32APIs. I am able to create the share, but I can't figure out how to set it's share permissions, I've tried a lot of things, but everything in documentation just goes deeper and deeper, to manage the Security Descriptor, DACL, ACL, etc...
I noticed you seem to have a very well written set of functions here and thought maybe you could help me. Here is what I have that works to create the share. It creates it with
Everyone: Full Control, Change, Read
. I would like to be able to setEveryone: Read
and that's it. I won't post now all the code I've tried, because it's probably just more confusing.