Skip to content

Instantly share code, notes, and snippets.

@Bill-Stewart
Created April 27, 2018 20:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Bill-Stewart/f8239e22a119c6451337df9d33bb7b17 to your computer and use it in GitHub Desktop.
Save Bill-Stewart/f8239e22a119c6451337df9d33bb7b17 to your computer and use it in GitHub Desktop.
Windows PowerShell module for managing access-based enumeration for shares
# 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
@jessiewestlake
Copy link

jessiewestlake commented Sep 13, 2019

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 set Everyone: Read and that's it. I won't post now all the code I've tried, because it's probably just more confusing.

$Signature = @'
using System;
using System.Runtime.InteropServices;

namespace Win32Api
{
    public class NetApi32
    {
        [DllImport("Netapi32.dll")]
        public static extern uint NetShareAdd(
            [MarshalAs(UnmanagedType.LPWStr)] string strServer,
            Int32 dwLevel,
            ref SHARE_INFO_502 buf,
            out uint parm_err
        );

        public enum SHARE_TYPE : uint
        {
            STYPE_DISKTREE = 0,
            STYPE_PRINTQ = 1,
            STYPE_DEVICE = 2,
            STYPE_IPC = 3,
            STYPE_TEMPORARY = 0x40000000,
            STYPE_SPECIAL = 0x80000000,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SHARE_INFO_502
        {
            [MarshalAs(UnmanagedType.LPWStr)] public string shi502_netname;
            public SHARE_TYPE shi502_type;
            [MarshalAs(UnmanagedType.LPWStr)] public string shi502_remark;
            public Int32 shi502_permissions;
            public Int32 shi502_max_uses;
            public Int32 shi502_current_uses;
            [MarshalAs(UnmanagedType.LPWStr)] public string shi502_path;
            [MarshalAs(UnmanagedType.LPWStr)] public string shi502_passwd;
            public Int32 shi502_reserved;
        }
    }
}
'@

Add-Type $Signature

$ShareName = "TEMP"
$shareDesc = "Test share."
$path = "C:\temp"

$info = [NetApi32+SHARE_INFO_502]::New()

$info.shi502_netname = $ShareName
$info.shi502_path = $path
$info.shi502_remark = $shareDesc
$info.shi502_max_uses = -1

$Result = [NetApi32]::NetShareAdd($null, 502, [ref] $info, [ref] 0)
$Result

@Bill-Stewart
Copy link
Author

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