FileShareUtils.psm1

# PowerShell Module FileShareUtils from Jean-Marc Ulrich
# https://github.com/CamFlyerCH/FileShareUtils
#
# Large parts of the used code where posted by
# Micky Balladelli micky@balladelli.com on https://balladelli.com/category/smb/
# and Alexander in his Kazun PowerShell blog https://kazunposh.wordpress.com/2011/12/11/%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-access-based-enumeration-%D1%81-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E-powershell/

# Code to access the netapi32 and advapi32 functions
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
 
public class Netapi
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_0
    {
        [MarshalAs(UnmanagedType.LPWStr)] public String Name;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_1
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        public uint Type;
        [MarshalAs(UnmanagedType.LPWStr)] public string Remark;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_2
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        public uint Type;
        [MarshalAs(UnmanagedType.LPWStr)] public string Remark;
        public uint Permissions;
        public uint MaxUses;
        public uint CurrentUses;
        [MarshalAs(UnmanagedType.LPWStr)] public string Path;
        [MarshalAs(UnmanagedType.LPWStr)] public string Password;
   }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_502
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        public uint Type;
        [MarshalAs(UnmanagedType.LPWStr)] public string Remark;
        public int Permissions;
        public int MaxUses;
        public int CurrentUses;
        [MarshalAs(UnmanagedType.LPWStr)] public string Path;
        [MarshalAs(UnmanagedType.LPWStr)] public string Password;
        public int Reserved;
        public IntPtr SecurityDescriptor;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_503
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        public uint Type;
        [MarshalAs(UnmanagedType.LPWStr)] public string Remark;
        public uint Permissions;
        public uint MaxUses;
        public uint CurrentUses;
        [MarshalAs(UnmanagedType.LPWStr)] public string Path;
        [MarshalAs(UnmanagedType.LPWStr)] public string Password;
        [MarshalAs(UnmanagedType.LPWStr)] public string ServerName;
        public uint Reserved;
        public IntPtr SecurityDescriptor;
    }
 
    [DllImport("Netapi32.dll",CharSet=CharSet.Unicode)]
    public static extern uint NetShareEnum(
        [In,MarshalAs(UnmanagedType.LPWStr)] string server,
        int level,
        out IntPtr bufptr,
        int prefmaxlen,
        ref Int32 entriesread,
        ref Int32 totalentries,
        ref Int32 resume_handle);
 
    [DllImport("Netapi32.dll",CharSet=CharSet.Unicode)]
    public static extern int NetApiBufferFree(IntPtr buffer);
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct STAT_SERVER_0
    {
      public uint Start;
      public uint FOpens;
      public uint DevOpens;
      public uint JobsQueued;
      public uint SOpens;
      public uint STimedOut;
      public uint SerrorOut;
      public uint PWerrors;
      public uint PermErrors;
      public uint SysRrrors;
      public uint bytesSent_low;
      public uint bytesSent_high;
      public uint bytesRcvd_low;
      public uint BytesRcvd_high;
      public uint AvResponse;
      public uint ReqNufNeed;
      public uint BigBufNeed;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct STAT_WORKSTATION_0
    {
      public long StatisticsStartTime;
      public long BytesReceived;
      public long SmbsReceived;
      public long PagingReadBytesRequested;
      public long NonPagingReadBytesRequested;
      public long CacheReadBytesRequested;
      public long NetworkReadBytesRequested;
      public long BytesTransmitted;
      public long SmbsTransmitted;
      public long PagingWriteBytesRequested;
      public long NonPagingWriteBytesRequested;
      public long CacheWriteBytesRequested;
      public long NetworkWriteBytesRequested;
      public uint InitiallyFailedOperations;
      public uint FailedCompletionOperations;
      public uint ReadOperations;
      public uint RandomReadOperations;
      public uint ReadSmbs;
      public uint LargeReadSmbs;
      public uint SmallReadSmbs;
      public uint WriteOperations;
      public uint RandomWriteOperations;
      public uint WriteSmbs;
      public uint LargeWriteSmbs;
      public uint SmallWriteSmbs;
      public uint RawReadsDenied;
      public uint RawWritesDenied;
      public uint NetworkErrors;
      public uint Sessions;
      public uint FailedSessions;
      public uint Reconnects;
      public uint CoreConnects;
      public uint Lanman20Connects;
      public uint Lanman21Connects;
      public uint LanmanNtConnects;
      public uint ServerDisconnects;
      public uint HungSessions;
      public uint UseCount;
      public uint FailedUseCount;
      public uint CurrentCommands;
    }
 
    [DllImport("Netapi32.dll",CharSet=CharSet.Unicode)]
    public static extern uint NetStatisticsGet(
        [In,MarshalAs(UnmanagedType.LPWStr)] string server,
        [In,MarshalAs(UnmanagedType.LPWStr)] string service,
        int level,
        int options,
        out IntPtr bufptr);
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SESSION_INFO_0
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SESSION_INFO_1
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        [MarshalAs(UnmanagedType.LPWStr)] public string Username;
        public uint NumOpens;
        public uint Time;
        public uint IdleTime;
        public uint UserFlags;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SESSION_INFO_2
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        [MarshalAs(UnmanagedType.LPWStr)] public string Username;
        public uint NumOpens;
        public uint Time;
        public uint IdleTime;
        public uint UserFlags;
        [MarshalAs(UnmanagedType.LPWStr)] public string ConnectionType;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SESSION_INFO_10
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        [MarshalAs(UnmanagedType.LPWStr)] public string Username;
        public uint Time;
        public uint IdleTime;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SESSION_INFO_502
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string Name;
        [MarshalAs(UnmanagedType.LPWStr)] public string Username;
        public uint NumOpens;
        public uint Time;
        public uint IdleTime;
        public uint UserFlags;
        [MarshalAs(UnmanagedType.LPWStr)] public string ConnectionType;
        [MarshalAs(UnmanagedType.LPWStr)] public string Transport;
    }
 
    [DllImport("Netapi32.dll",CharSet=CharSet.Unicode)]
    public static extern uint NetSessionEnum(
        [In,MarshalAs(UnmanagedType.LPWStr)] string server,
        int client,
        int user,
        int level,
        out IntPtr bufptr,
        int prefmaxlen,
        ref Int32 entriesread,
        ref Int32 totalentries,
        ref Int32 resume_handle);
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct FILE_INFO_2
    {
        public uint FileID;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct FILE_INFO_3
    {
        public uint FileID;
        public uint Permissions;
        public uint NumLocks;
        [MarshalAs(UnmanagedType.LPWStr)] public string Path;
        [MarshalAs(UnmanagedType.LPWStr)] public string User;
    }
 
    [DllImport("Netapi32.dll",CharSet=CharSet.Unicode)]
    public static extern uint NetFileEnum(
        [In,MarshalAs(UnmanagedType.LPWStr)] string server,
        [In,MarshalAs(UnmanagedType.LPWStr)] string path,
        int user,
        int level,
        out IntPtr bufptr,
        int prefmaxlen,
        ref Int32 entriesread,
        ref Int32 totalentries,
        ref Int32 resume_handle);
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    public static extern bool GetSecurityDescriptorDacl(
        IntPtr pSecurityDescriptor,
        [MarshalAs(UnmanagedType.Bool)] out bool bDaclPresent,
        ref IntPtr pDacl,
        [MarshalAs(UnmanagedType.Bool)] out bool bDaclDefaulted
    );
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    public static extern bool GetAclInformation(
        IntPtr pAcl,
        ref ACL_SIZE_INFORMATION pAclInformation,
        uint nAclInformationLength,
        ACL_INFORMATION_CLASS dwAclInformationClass
    );
  
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetAce(
        IntPtr aclPtr,
        int aceIndex,
        out IntPtr acePtr
    );
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetLengthSid(
        IntPtr pSID
    );
     
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ConvertSidToStringSid(
        [MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
        out IntPtr ptrSid
    );
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool IsValidSecurityDescriptor(
        IntPtr pSecurityDescriptor
    );
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    public static extern bool ConvertSecurityDescriptorToStringSecurityDescriptor(
        IntPtr pSecurityDescriptor,
        int SDRevision,
        int SecurityInfo,
        out IntPtr StringSDL,
        out int StringLength
    );
 
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int ConvertStringSecurityDescriptorToSecurityDescriptor(
        IntPtr StringSDL,
        int SDRevision,
        out IntPtr pSecurityDescriptor,
        out int pSecurityDescriptorSize
    );
 
 
    [StructLayout(LayoutKind.Sequential)]
    public struct ACL_SIZE_INFORMATION
    {
        public uint AceCount;
        public uint AclBytesInUse;
        public uint AclBytesFree;
    }
 
    [StructLayout(LayoutKind.Sequential)]
    public struct ACE_HEADER
    {
        public byte AceType;
        public byte AceFlags;
        public short AceSize;
    }
 
    [StructLayout(LayoutKind.Sequential)]
    public struct ACCESS_ALLOWED_ACE
    {
        public ACE_HEADER Header;
        public int Mask;
        public int SidStart;
    }
 
    public enum ACL_INFORMATION_CLASS
    {
        AclRevisionInformation = 1,
        AclSizeInformation
    }
 
    public enum CacheType : uint
    {
        Manual = 0x00,
        Documents = 0x10,
        Programs = 0x20,
        None = 0x30,
    }
  
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHARE_INFO_1005
    {
        public uint shi1005_flags;
    }
 
    public const int FLAGS_ACCESS_BASED_DIRECTORY_ENUM = 0x0800;
 
    public enum Share_Type : uint
    {
        STYPE_DISKTREE = 0x00000000, // Disk Drive
        STYPE_PRINTQ = 0x00000001, // Print Queue
        STYPE_DEVICE = 0x00000002, // Communications Device
        STYPE_IPC = 0x00000003, // InterProcess Communications
        STYPE_SPECIAL = 0x80000000, // Special share types (C$, ADMIN$, IPC$, etc)
        STYPE_TEMPORARY = 0x40000000 // Temporary share
    }
  
    public enum LogicalShareRights : uint
    {
        FullControl = 0x001f01ff,
        Read = 0x001200a9,
        Change = 0x001301bf
    }
 
    [DllImport("Netapi32.dll", SetLastError=true)]
    public static extern int NetShareGetInfo(
        [MarshalAs(UnmanagedType.LPWStr)] string serverName,
        [MarshalAs(UnmanagedType.LPWStr)] string netName,
        Int32 level,
        out IntPtr bufPtr );
  
    [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int NetShareSetInfo(
        [MarshalAs(UnmanagedType.LPWStr)] string serverName,
        [MarshalAs(UnmanagedType.LPWStr)] string netName,
        Int32 level,
        IntPtr buf,
        IntPtr parm_err );
 
    [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int NetShareAdd(
        [MarshalAs(UnmanagedType.LPWStr)] string serverName,
        Int32 level,
        IntPtr buf,
        IntPtr parm_err );
 
    [DllImport("Netapi32.dll", SetLastError=true)]
    public static extern int NetShareDel(
        [MarshalAs(UnmanagedType.LPWStr)] string serverName,
        [MarshalAs(UnmanagedType.LPWStr)] string netName,
        Int32 reserved);
  
    }
"@
 

# Helper - Functions ===========================================================================

Function Get-NetAPIReturnInfo($CallReturn){
    ([ComponentModel.Win32Exception][Int32]$CallReturn).Message
    #net helpmsg $CallReturn
}

Function Get-DNSGet-DNSReverseLookup{
    Param(
        [Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$IP
    )
    Process {
        IF($IP.Contains(".")){
            Trap{$IP;continue}
            [System.Net.Dns]::GetHostEntry($IP).HostName
        } Else {
            $IP
        }
    }
}

Function Convert-ShareACLToText($ShareACL,$SortACL){
    #Seperators in the String
    $InACLseperator = ","
    $InACEseperator = "|"
    $ShareACLText = ""

    $ShareACLArray = $ShareACL.Access

    If($ShareACLArray.Count -ne 0){
        If($SortACL){
            $ShareACLArray = $ShareACLArray | Sort-Object -Property @{Expression="AccessControlType";Descending=$true}, @{Expression="IdentityReference";Descending=$false}
        }

        # Build string
        ForEach($ShareACE in $ShareACLArray){
            $AccessRight = $ShareACE.FileSystemRights
            If($AccessRight -eq "ReadAndExecute, Synchronize") {$AccessRight = "Read"}
            If($AccessRight -eq "Modify, Synchronize") {$AccessRight = "Change"}
            If($ShareACE.AccessControlType -ne "Allow") {$AccessRight = "Deny-" + $AccessRight}

            $ShareACLText += ($ShareACE.IdentityReference.Value + $InACEseperator + $AccessRight + $InACLseperator)
        }

        # Cut last seperator
        $Return = ($ShareACLText.SubString(0,$ShareACLText.Length - 1))
    } Else {$Return = ""}
    $Return
}

Function Convert-ACLTextToShareACL($UnsortedACLText){
    #Seperators in the String
    $InACLseperator = ","
    $InACEseperator = "|"
    $NewACLObject = New-Object -TypeName System.Security.AccessControl.DirectorySecurity

    $ACETexts = $UnsortedACLText -split $InACLseperator

    ForEach($ACEText in $ACETexts){
        If($ACEText){
            $OutputIdentityReference = $ACEText.substring(0,$ACEText.indexof($InACEseperator))
            $OutputFileSystemRights = $ACEText.substring($ACEText.indexof($InACEseperator)+$InACEseperator.length,$ACEText.length-($ACEText.indexof($InACEseperator)+$InACEseperator.length))

            If($OutputFileSystemRights.substring(0,4) -eq "Deny"){
                $OutputFileSystemRights = "FullControl"
                $OutputAccessControlType = "Deny"
            } Else {
                $OutputAccessControlType = "Allow"
            }

            If($OutputFileSystemRights -eq "Read") {$OutputFileSystemRights = "ReadAndExecute, Synchronize"}
            If($OutputFileSystemRights -eq "Change") {$OutputFileSystemRights = "Modify, Synchronize"}

            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($OutputIdentityReference, $OutputFileSystemRights, "None", "None", $OutputAccessControlType)
            Try{
                $NewACLObject.AddAccessRule($rule)
            } Catch {
                $ErrorMessage = $error[0].ToString()
                Throw ("Error for Identity $OutputIdentityReference : " + $ErrorMessage)
            }
        }
    }
    $NewACLObject
}

Function Convert-SDtoSDDL($ptr2SD,$SECURITY_INFORMATION){
    #OWNER_SECURITY_INFORMATION 1 der Eigentümer wird konvertiert
    #GROUP_SECURITY_INFORMATION 2 die primäre Gruppe wird konvertiert
    #DACL_SECURITY_INFORMATION 4 die DACL Zugriffskontrollliste wird konvertiert
    #SACL_SECURITY_INFORMATION 8 die System Zugriffskontrollliste wird konvertiert
    #LABEL_SECURITY_INFORMATION 16 die Mandantory Zugriffskontrolleinträge werden konvertiert
    #$SECURITY_INFORMATION = 4
    $SDDL_REVISION_1 = 1

    $return = [Netapi]::IsValidSecurityDescriptor($ptr2SD)

    $sddlptr = [IntPtr]::Zero
    $sddllength = [IntPtr]::Zero
    $return = [Netapi]::ConvertSecurityDescriptorToStringSecurityDescriptor($ptr2SD,$SDDL_REVISION_1,$SECURITY_INFORMATION,[ref]$sddlptr,[ref]$sddllength)
    If($return -ne $True){
        Throw ("Error during ConvertSecurityDescriptorToStringSecurityDescriptor: " + (Get-NetAPIReturnInfo $return))
    }
    $sddl = [System.Runtime.Interopservices.Marshal]::PtrToStringAuto($sddlptr)
    $sddl
}

Function Convert-SDDLToACL {
    <#
        .Synopsis
            Convert SDDL String to ACL Object
        .DESCRIPTION
            Converts one or more SDDL Strings to a standard powershell format.
        .EXAMPLE
            Convert-SDDLToACL -SDDLString (get-acl .\path).sddl
        .EXAMPLE
            Convert-SDDLToACL -SDDLString “O:S-1-5-21-1559760989-2529464504-629046386-3226G:DUD:(A;OICIID;FA;;;SY)(A;OICIID;FA;;;BA)(A;OICIID;FA;;;S-1-5-21-1557760989-2587764504-629046386-1166)”
        .NOTES
            Robert Amartinesei
            https://poshscripter.wordpress.com/2017/04/27/sddl-conversion-with-powershell/
    #>

    [Cmdletbinding()]
    param (
        #One or more strings of SDDL syntax.
        [string[]]$SDDLString
    )
    foreach ($SDDL in $SDDLString) {
        $ACLObject = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
        $ACLObject.SetSecurityDescriptorSddlForm($SDDL)
        $ACLObject
    }
}




# Module - Functions =============================================================================



Function Get-NetShares{
    <#
        .SYNOPSIS
            Retrieves a list of all network shares from a local or remote computer
 
        .DESCRIPTION
            Retrieves basic parameters of all shares from a local or remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .NOTES
            Name: Get-NetShares
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 10.05.2018
                1.1 //Changed to support servers with more than ~250 shares
 
        .EXAMPLE
            Get-NetShares -Server 'srv1234'
 
            Description
            -----------
            Gets the information of all shares of a remote server
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower()
    )
    Begin {
        # We want to query the 502 datas
        $buffer = 0
        $entries = 0
        $total = 0
        $handle = 0
        $Shares = @()
        $struct = New-Object Netapi+SHARE_INFO_502
        $ReadFinished = $False

        While($ReadFinished -eq $False){
            $return = [Netapi]::NetShareEnum($Server,502,[ref]$buffer,-1,[ref]$entries, [ref]$total,[ref]$handle) 

            If($return -ne 0 -and $return -ne 234){
               Throw ("Error during NetShareEnum: " + (Get-NetAPIReturnInfo $return))
            }

            If($entries -eq $total -and $return -eq 0){$ReadFinished = $True}

            $offset = $buffer.ToInt64()
            $increment = [System.Runtime.Interopservices.Marshal]::SizeOf([System.Type]$struct.GetType())

            For ($i = 0; $i -lt $entries; $i++)
            {
                # Define output object
                $Share = New-Object -TypeName PSObject
                $ptr = New-Object system.Intptr -ArgumentList $offset
                
                $str502 = [system.runtime.interopservices.marshal]::PtrToStructure($ptr, [System.Type]$struct.GetType())
                $offset = $ptr.ToInt64()
                $offset += $increment

                # Get the easy data
                $Share | Add-Member Server $Server
                $Share | Add-Member Name $str502.Name
                $Share | Add-Member Path $str502.Path
                $Share | Add-Member Description $str502.Remark
                $Share | Add-Member CurrentUses $str502.CurrentUses

                # Get the type
                If (($str502.Type -band 0x00000001) -ne 0 -and ($str502.Type -band 0x00000002) -ne 0){
                    $Type = "IPC"
                } Else {
                    If ($str502.Type -band 0x00000001){
                        $Type = "Print Queue"
                    } Else {
                        If ($str502.Type -band 0x00000002){
                            $Type = "Device"
                        } Else {
                            $Type = "Disk Drive"
                        }
                    }
                }
                If (($str502.Type -band 0x40000000) -ne 0){
                    $Type = $Type + " Temporary"
                }
                If (($str502.Type -band 0x80000000) -ne 0){
                    $Type = $Type + " Special"
                }
                $Share | Add-Member Type $Type

                $Shares += $Share
            }
        }

        $Shares | Sort-Object Path

        # Cleanup memory
        [Netapi]::NetApiBufferFree($buffer) | Out-Null
    }
}


Function Get-NetShare{
    <#
        .SYNOPSIS
            Retrieves specified network share information from a local or remote computer
 
        .DESCRIPTION
            Retrieves all parameters and permissions form one defined network share
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Name
            Name of the share
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .NOTES
            Name: Get-NetShare
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 10.05.2018
 
        .EXAMPLE
            Get-NetShare -Name 'TestShare' -Server 'srv1234'
 
            Description
            -----------
            Gets the information of the share named "TestShare" of a remote server
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Name,

        [Parameter(Position=1,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower()
    )
    Begin {
        # We want to query the 502 data first
        $bufptr = [IntPtr]::Zero
        $struct = New-Object Netapi+SHARE_INFO_502
        $return = [Netapi]::NetShareGetInfo($Server,$Name,502,[ref]$bufptr)

        If($return -eq 0){
            $str502 = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufptr,[System.Type]$struct.GetType())
        } Else {
            Throw ("Error during NetShareGetInfo for $Name : " + (Get-NetAPIReturnInfo $return))
        }

        # Now read the flags
        $bufptr = [IntPtr]::Zero
        $struct = New-Object Netapi+SHARE_INFO_1005
        $return = [Netapi]::NetShareGetInfo($Server,$Name,1005,[ref]$bufptr)

        If($return -eq 0){
            $str1005 = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufptr,[System.Type]$struct.GetType())
        } Else {
            Throw ("Error during NetShareGetInfo 1005: " + (Get-NetAPIReturnInfo $return))
        }

        # Define output object
        $Share = New-Object -TypeName PSObject

        # Get the easy data
        $Share | Add-Member Server $Server
        $Share | Add-Member Name $str502.Name
        $Share | Add-Member Path $str502.Path
        $Share | Add-Member Description $str502.Remark

        # Get infos contained in the flags
        $ShareFlags = $str1005.shi1005_flags

        If ($ShareFlags -band 0x0800){
            $Share | Add-Member ABE "Enabled"
        } Else {
            $Share | Add-Member ABE "Disabled"
        }
        
        # Offline (client side caching) configuration
        $Share | Add-Member CachingMode ([enum]::GetValues([Netapi+CacheType]) | Where-Object {$_.value__ -eq ($ShareFlags -band 0x0030)})
        
        # Prepare to read ACL
        If ($str502.SecurityDescriptor -ne 0){

            #$ShareACL = SDtoACL $str502.SecurityDescriptor
            #$ShareACLText = ACLArrayToACLText $ShareACL $True
            $ShareACLSSDL = Convert-SDtoSDDL $str502.SecurityDescriptor 4 # 15 or 4
            $ShareACL = Convert-SDDLToACL -SDDLString $ShareACLSSDL
            #$ShareACLText = $ShareACL.AccessToString
            $ShareACLText = Convert-ShareACLToText $ShareACL $True

        } # EndIf SD present


        $Share | Add-Member ShareACLText $ShareACLText

        $Share | Add-Member CurrentUses $str502.CurrentUses
        $Share | Add-Member ConcurrentUserLimit $str502.MaxUses

        If ($ShareFlags -band 0x2000){
            $Share | Add-Member BranchCache "Enabled"
        } Else {
            $Share | Add-Member BranchCache "Disabled"
        }
        
        $Share | Add-Member Flags $ShareFlags

        # Get the type
        Switch ($str502.Type){
            0x00000000    {$Share | Add-Member Type "Disk Drive"}
            0x00000001    {$Share | Add-Member Type "Print Queue"}
            0x00000002    {$Share | Add-Member Type "Device"}
            0x00000003    {$Share | Add-Member Type "IPC"}
            0x80000000    {$Share | Add-Member Type "Special"}
            0x40000000    {$Share | Add-Member Type "Temporary"}
        }

        $Share | Add-Member ShareSDDL $ShareACLSSDL

        $Share | Add-Member ShareACL $ShareACL

        # Cleanup memory
        [Netapi]::NetApiBufferFree($bufptr) | Out-Null

        $Share

    }
}

Function Get-NetFileShares{
    <#
        .SYNOPSIS
            Retrieves a list of all file shares from a local or remote computer with permissions and flags
 
        .DESCRIPTION
            Retrieves basic parameters of all shares from a local or remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .NOTES
            Name: Get-NetFileShares
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 10.05.2018
 
        .EXAMPLE
            Get-NetFileShares -Server 'srv1234'
 
            Description
            -----------
            Gets the information of all shares of a remote server
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower()
    )    
    Begin {
        $FileShareList = Get-NetShares -Server $Server | Sort-Object Server,Path,Name

        ForEach ($FileShare in $FileShareList){
            IF ($FileShare.Type -eq "Disk Drive"){
                Get-NetShare -Name $FileShare.Name -Server $FileShare.Server | Select-Object -Property Server,Name,Path,Description,ABE,CachingMode,ShareACLText,CurrentUses
            }
        }
    }
}


Function New-NetShare{
    <#
        .SYNOPSIS
            Creates a network file share local or on a remote computer
 
        .DESCRIPTION
            Creates a network share
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .PARAMETER Name
            Name of the share
 
        .PARAMETER Path
            Local disk path to share
 
        .PARAMETER Description
            The description/remark of the share
 
        .PARAMETER Permissions
            Permissions on the share itself. Speical format: Every permission is seperated by a comma and the identity and the access right are seperated by a |
            Default: Everyone|FullControl
            Possible Permissions: Read, Change, FullControl, Deny-FullControl
            Possible Identities: Everyone, BUILTIN\Administrators, BUILTIN\Users, BUILTIN\xxxxx (server local users or groups), DOMAIN\UserName, ADCORP\GroupName, <NETBIOSDOMAINNAME>\<sAMAccountName> (domain objects)
 
        .PARAMETER ABE
            Access based enumaration, can be Enabled or Disabled (default)
 
        .PARAMETER CachingMode
            Offline Folder configuration, can be Manual (default), "None", "Documents" (all documents are automaticaly offline available), "Programs" ("Performance option", all files are automaticaly offline available)
 
        .PARAMETER MaxUses
            Allowed connections to the share. Default is -1 that equals maximum
 
 
        .NOTES
            Name: New-NetShare
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 17.05.2018
 
        .EXAMPLE
            New-NetShare -Server 'srv1234' -Name 'TestShare' -Path 'D:\Data'
 
            Description
            -----------
            Shares the path D:\Data on the server named srv1234 as TestShare
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower(),

        [Parameter(Position=1,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Name,

        [Parameter(Position=2,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Path,

        [Parameter(Position=3,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Description,

        [Parameter(Position=4,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Permissions,  # = "Everyone|FullControl"

        [Parameter(Position=5,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [ValidateSet("Disabled", "Enabled", IgnoreCase = $true)]  # FolderEnumerationMode AccessBased, Unrestricted
        [string]$ABE = "Disabled",

        [Parameter(Position=6,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [ValidateSet("Manual", "None", "Documents", "Programs", IgnoreCase = $true)]
        [string]$CachingMode = "Manual",

        [Parameter(Position=7,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Int]$MaxUses = -1

    )
    Begin {
        $struct = New-Object Netapi+SHARE_INFO_502
        $struct.Name = $Name
        $struct.Type = 0                 # 0 -> STYPE_DISKTREE
        $struct.Path = $Path
        $struct.Remark = $Description
        $struct.Permissions = 0
        $struct.MaxUses = $MaxUses
        $paramerror = 0

        $structsize = [System.Runtime.InteropServices.Marshal]::SizeOf($struct)
        [IntPtr]$bufptr = [System.Runtime.InteropServices.Marshal]::AllocCoTaskMem($structsize)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($struct,$bufptr,$false)

        $return = [Netapi]::NetShareAdd($Server,502,$bufptr,$paramerror)

        [Netapi]::NetApiBufferFree($bufptr) | Out-Null

        If($return -ne 0){
            Throw ("Error during NetShareAdd: " + (Get-NetAPIReturnInfo $return))
        }

        $return = Set-NetShare -Server $Server -Name $Name -Description $Description -Permissions $Permissions -ABE $ABE -CachingMode $CachingMode -MaxUses $MaxUses

    }
}

Function Set-NetShare{
    <#
        .SYNOPSIS
            Changes options on a network file share local or on a remote computer
 
        .DESCRIPTION
            Can modify all changeable options of a file share local or on a remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .PARAMETER Name
            Name of the share
 
        .PARAMETER Description
            The description/remark of the share
 
        .PARAMETER Permissions
            Permissions on the share itself. Speical format: Every permission is seperated by a comma and the identity and the access right are seperated by a |
            Default: Everyone|FullControl
            Possible Permissions: Read, Change, FullControl, Deny-FullControl
            Possible Identities: Everyone, BUILTIN\Administrators, BUILTIN\Users, BUILTIN\xxxxx (server local users or groups), DOMAIN\UserName, ADCORP\GroupName, <NETBIOSDOMAINNAME>\<sAMAccountName> (domain objects)
 
        .PARAMETER ABE
            Access based enumaration, can be Enabled or Disabled (default)
 
        .PARAMETER CachingMode
            Offline Folder configuration, can be Manual (default), "None", "Documents" (all documents are automaticaly offline available), "Programs" ("Performance option", all files are automaticaly offline available)
 
        .PARAMETER MaxUses
            Allowed connections to the share. Default is -1 that equals maximum
 
        .NOTES
            Name: Set-NetShare
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 17.05.2018
 
        .EXAMPLE
            Set-NetShare -Server 'srv1234' -Name 'TestShare' -Description "A test share" -ABE Enabled -CachingMode None -MaxUses 50 -Permissions "DOMAINNAME\Domain Admins|FullControl,Everyone|Change,BUILTIN\Administrators|FullControl"
 
            Description
            -----------
            Sets the given options and permissions a server named srv1234 on the share named TestShare
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower(),

        [Parameter(Position=1,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Name,

        [Parameter(Position=2,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Description,

        [Parameter(Position=3,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Permissions,

        [Parameter(Position=4,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [ValidateSet("Disabled", "Enabled", IgnoreCase = $true)]  # FolderEnumerationMode AccessBased, Unrestricted
        [string]$ABE,

        [Parameter(Position=5,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [ValidateSet("Manual", "None", "Documents", "Programs", IgnoreCase = $true)]
        [string]$CachingMode,

        [Parameter(Position=6,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Int]$MaxUses

    )
    Begin {
        # Read all existing share informations

        # We want to query the 502 data first
        $bufptr502 = [IntPtr]::Zero
        $struct502 = New-Object Netapi+SHARE_INFO_502
        $return = [Netapi]::NetShareGetInfo($Server,$Name,502,[ref]$bufptr502)

        If($return -eq 0){
            $str502 = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufptr502,[System.Type]$struct502.GetType())
        } Else {
            Throw ("Error during NetShareGetInfo for $Name on $Server : " + (Get-NetAPIReturnInfo $return))
        }

        # Now read the flags
        $bufptr1005 = [IntPtr]::Zero
        $struct1005 = New-Object Netapi+SHARE_INFO_1005
        $return = [Netapi]::NetShareGetInfo($Server,$Name,1005,[ref]$bufptr1005)

        If($return -eq 0){
            $str1005 = [System.Runtime.InteropServices.Marshal]::PtrToStructure($bufptr1005,[System.Type]$struct1005.GetType())
        } Else {
            Throw ("Error during NetShareGetInfo 1005 for $Name on $Server : " + (Get-NetAPIReturnInfo $return))
        }
        $ShareFlags = $str1005.shi1005_flags

        # Prepare for modifications
        $Write502 = $False
        $Write1005 = $False
        $paramerror = 0

        # Check for changes in the 502 structure
        If($PSBoundParameters.ContainsKey('Description')){
            If($str502.Remark -ne $Description){
                $str502.Remark = $Description
                $Write502 = $True
            }
        }

        If($PSBoundParameters.ContainsKey('MaxUses')){
            If($str502.MaxUses -ne $MaxUses){
                $str502.MaxUses = $MaxUses
                $Write502 = $True
            }
        }

        If($PSBoundParameters.ContainsKey('Permissions')){
            If($Permissions){
                # Cenvert and sort given permissions
                $NewACL = Convert-ACLTextToShareACL $Permissions
                $NewShareACLText = Convert-ShareACLToText $NewACL $True
                
                If ($str502.SecurityDescriptor -ne 0){

                    $ShareACLSSDL = Convert-SDtoSDDL $str502.SecurityDescriptor 4 # 15 or 4
                    $ShareACL = Convert-SDDLToACL -SDDLString $ShareACLSSDL
                    $ShareACLText = Convert-ShareACLToText $ShareACL $True
                }

                If($NewShareACLText -ne $ShareACLText){

                    $sddlptr = [IntPtr]::Zero
                    $sddlptr = [System.Runtime.Interopservices.Marshal]::StringToHGlobalAuto($NewACL.Sddl)

                    $SDDL_REVISION_1 = 1
                    $NewSDptr = [IntPtr]::Zero
                    $NewSDsize = [IntPtr]::Zero
                    $return = [Netapi]::ConvertStringSecurityDescriptorToSecurityDescriptor($sddlptr,$SDDL_REVISION_1,[ref]$NewSDptr,[ref]$NewSDsize)
                    If($return -ne $True){
                        Throw ("Error during ConvertStringSecurityDescriptorToSecurityDescriptor: " + (Get-NetAPIReturnInfo $return))
                    }

                    $str502.SecurityDescriptor = $NewSDptr
                    $Write502 = $True
                }
            }
        }


        # Write 502 data if changed
        If($Write502){
            $structsize = [System.Runtime.InteropServices.Marshal]::SizeOf($str502)
            [IntPtr]$bufptr = [System.Runtime.InteropServices.Marshal]::AllocCoTaskMem($structsize)
            [System.Runtime.InteropServices.Marshal]::StructureToPtr($str502,$bufptr,$false)

            $return = [Netapi]::NetShareSetInfo($Server,$Name,502,$bufptr,$paramerror)

            If($return -ne 0){
                Throw ("Error during NetShareSetInfo 502: " + (Get-NetAPIReturnInfo $return))
            }
        }


        # Check for changes in the 1005 flags
        If($PSBoundParameters.ContainsKey('ABE')){
            If($ShareFlags -band [Netapi]::FLAGS_ACCESS_BASED_DIRECTORY_ENUM -AND $ABE -eq "Disabled"){
                $ShareFlags = $ShareFlags -bxor [Netapi]::FLAGS_ACCESS_BASED_DIRECTORY_ENUM
                $Write1005 = $True
            }
            If(!($ShareFlags -band [Netapi]::FLAGS_ACCESS_BASED_DIRECTORY_ENUM) -AND $ABE -eq "Enabled"){
                $ShareFlags = $ShareFlags -bor [Netapi]::FLAGS_ACCESS_BASED_DIRECTORY_ENUM
                $Write1005 = $True
            }
        }

        If($PSBoundParameters.ContainsKey('CachingMode')){
            $NewCachingFlags = [Netapi+CacheType]::($Cachingmode).value__
            If (($ShareFlags -band 0x0030) -ne $NewCachingFlags){
                $ShareFlags = $ShareFlags -bxor ($ShareFlags -band 0x0030) -bor $NewCachingFlags
                $Write1005 = $True
            }
        }

        # Write 1005 data if changed
        If($Write1005){
            $str1005.shi1005_flags = $ShareFlags
            $structsize = [System.Runtime.InteropServices.Marshal]::SizeOf($str1005)
            [IntPtr]$bufptr = [System.Runtime.InteropServices.Marshal]::AllocCoTaskMem($structsize)
            [System.Runtime.InteropServices.Marshal]::StructureToPtr($str1005,$bufptr,$false)

            $return = [Netapi]::NetShareSetInfo($Server,$Name,1005,$bufptr,$paramerror)

            [Netapi]::NetApiBufferFree($bufptr) | Out-Null

            If($return -ne 0){
                Throw ("Error during NetShareSetInfo 1005: " + (Get-NetAPIReturnInfo $return))
            }
        }

        # Cleanup memory
        [Netapi]::NetApiBufferFree($bufptr502) | Out-Null
        [Netapi]::NetApiBufferFree($bufptr1005) | Out-Null

    }
}

Function Remove-NetShare{
    <#
        .SYNOPSIS
            Deletes a network file share local or on a remote computer
 
        .DESCRIPTION
            Removes / stops a share localy or on a remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .PARAMETER Name
            Name of the share
 
        .NOTES
            Name: Remove-NetShare
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 18.05.2018
 
        .EXAMPLE
            Remove-NetShare -Server 'srv1234' -Name 'TestShare'
 
            Description
            -----------
            Deletes the share named TestShare on the server named srv1234
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower(),

        [Parameter(Position=1,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Name
    )
    Begin {
        $reserved = 0
        $return = [Netapi]::NetShareDel($Server,$Name,$reserved)

        If($return -ne 0){
            Throw ("Error during NetShareDel for $Name on $Server : " + (Get-NetAPIReturnInfo $return))
        }
    }
}


Function Get-NetSessions{
    <#
        .SYNOPSIS
            Retrieves all open server sessions from a local or remote computer
 
        .DESCRIPTION
            Retrieves all usefull session informations from a local or remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .NOTES
            Name: Get-NetSessions
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 11.05.2018
 
            OUTPUT : Array of objects with Username,Client,Opens (open files),TimeTS (as timespan),Time (as string),Connected (as DateTime),IdleTS (as timespan),Idle (as string),IdleSince (as DateTime),ConnectionType
 
 
        .EXAMPLE
            Get-NetSessions -Server 'srv1234'
 
            Description
            -----------
            Gets the information of all shares of a remote server
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower()
    )
    Begin {
        $level = 502
        $struct = New-Object netapi+SESSION_INFO_502

        $buffer = 0
        $entries = 0
        $total = 0
        $handle = 0
        $now = Get-Date
        $Sessions = @()
        $client = $null # for NetAPP compatibility
        $user = $null # for NetAPP compatibility
        $ReadFinished = $False

        While($ReadFinished -eq $False){
        
            $return = [Netapi]::NetSessionEnum($server, $client, $user, $level,[ref]$buffer, -1,[ref]$entries, [ref]$total,[ref]$handle)

            If($return -ne 0 -and $return -ne 234){
                Throw ("Error during NetSessionEnum: " + (Get-NetAPIReturnInfo $return))
            }

            If($entries -eq $total -and $return -eq 0){$ReadFinished = $True}

            $offset = $buffer.ToInt64()
            $increment = [System.Runtime.Interopservices.Marshal]::SizeOf([System.Type]$struct.GetType())

            for ($i = 0; $i -lt $entries; $i++)
            {
                $ptr = New-Object system.Intptr -ArgumentList $offset
                $Session = [system.runtime.interopservices.marshal]::PtrToStructure($ptr, [System.Type]$struct.GetType())

                $offset = $ptr.ToInt64()
                $offset += $increment

                # Resolve hostname
                $Clientname = Get-DNSGet-DNSReverseLookup $Session.Name

                $TimeTS = New-TimeSpan -Seconds $Session.Time
                $Time = [String]([Int]$TimeTS.TotalHours) + ":" + [String]($TimeTS.Minutes)
                $Connected = ($Now - $TimeTS)       # .ToString('yyyy-MM-dd HH:mm')

                $IdleTS = New-TimeSpan -Seconds $Session.IdleTime
                $Idle = [String]([Int]$IdleTS.TotalHours) + ":" + [String]($IdleTS.Minutes)
                $IdleSince = ($Now - $IdleTS)       # .ToString('yyyy-MM-dd HH:mm')

                $Sessions += $Session | Select-Object -Property Username,@{n='Client';e={$Clientname}},@{n='Opens';e={$_.NumOpens}},@{n='TimeTS';e={$TimeTS}},@{n='Time';e={$Time}},@{n='Connected';e={$Connected}},@{n='IdleTS';e={$IdleTS}},@{n='Idle';e={$Idle}},@{n='IdleSince';e={$IdleSince}},ConnectionType 
            }
        }

        $Sessions | Sort-Object Username,Client

        # Cleanup memory
        [Netapi]::NetApiBufferFree($buffer) | Out-Null
    }

}


Function Get-NetOpenFiles{
    <#
        .SYNOPSIS
            Retrieves all open files from a local or remote computer
 
        .DESCRIPTION
            Retrieves all usefull informations about open files from a local or remote computer
            by using the NetAPI32.dll (without WMI)
 
        .PARAMETER Server
            Computername on the network, local if left blank
 
        .PARAMETER Path
            Beginning (left part) of a server local path to filter the results, if left undefined all open files are listed
 
        .NOTES
            Name: Get-NetOpenFiles
            Author: Jean-Marc Ulrich
            Version History:
                1.0 //First version 11.05.2018
 
            OUTPUT : Array of objects with Username,Client,Opens (open files),TimeTS (as timespan),Time (as string),Connected (as DateTime),IdleTS (as timespan),Idle (as string),IdleSince (as DateTime),ConnectionType
 
 
        .EXAMPLE
            Get-NetOpenFiles -Server 'srv1234' -Path "C:\vol_fs_cifs_srv001234_001\data\Progs"
 
            Description
            -----------
            Gets the information of open files in a specific folder
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Server = ($env:computername).toLower(),
        [Parameter(Position=0,Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [string]$Path = $Null
    )
    Begin {
        $level = 3
        $struct = New-Object netapi+FILE_INFO_3 

        $buffer = 0
        $entries = 0
        $total = 0
        $handle = 0
        $Files = @()
        $user = $null # for NetAPP compatibility
        $ReadFinished = $False

        While($ReadFinished -eq $False){

            $return = [Netapi]::NetFileEnum($server,$path,$user,$level,[ref]$buffer,-1,[ref]$entries, [ref]$total,[ref]$handle) 

            If($return -ne 0 -and $return -ne 234){
                Throw ("Error during NetFileEnum: " + (Get-NetAPIReturnInfo $return))
            }

            If($entries -eq $total -and $return -eq 0){$ReadFinished = $True}

            $offset = $buffer.ToInt64()
            $increment = [System.Runtime.Interopservices.Marshal]::SizeOf([System.Type]$struct.GetType())

            for ($i = 0; $i -lt $entries; $i++)
            {
                $ptr = New-Object system.Intptr -ArgumentList $offset
                $File = [system.runtime.interopservices.marshal]::PtrToStructure($ptr, [System.Type]$struct.GetType())

                $offset = $ptr.ToInt64()
                $offset += $increment

                Switch ($File.Permissions){
                    1 { $Access = "Read" }
                    2 { $Access = "Write" }
                    3 { $Access = "Write" }
                    4 { $Access = "Create" }

                    default { $Access = "" }
                }

                $Files += $File | Select-Object Path,User,@{n='Access';e={$Access}},@{n='Locks';e={$File.NumLocks}}

            }
        }
        
        $Files | Sort-Object Path,User

        # Cleanup memory
        [Netapi]::NetApiBufferFree($buffer) | Out-Null

    }
}