Get-share

Don’t get me wrong with my previous post. I really appreciate Richard’s work and I’ve been following his blogs for years now. A few years ago, I got inspired by his work on shares that he published in the PAM module on codeplex and that can be downloaded on this page. I extended his code to be able to create shares on remote servers in our user account creation process.

My original idea was actually to be able to replace our brave old (and still working) rmtshare.exe from the NT4.0 resource kit. In other words I wanted to be able to provide the same functionnalities as the ‘net share’ command and the ‘rmtshare’ commmand.

Here are some examples of what I wanted to achieve:
net_share_and_rmtshare_output

WMI is very resourceful in this case but it’s unfortunately unable to get the caching mode of each share. The only way to do it that I found on the web is by using the code provided on this page. (NB: I’ve not tested it). Of course, using the WMI approach will be much slower than using API calls, but it’s not the main goal.

To be able to display the users currently accessing the share I’ve used the Win32_ConnectionShare class and the [WMI] type accelerator. The power of WMI is that the Dependent key contains a full path including the computername and the WMI accelerator is able to work accross the network. The only drawback of the WMI accelerator is that you can’t specify a credential to access a remote computer.

For share permissions, I took a different approach than Richard’s one. I query the Win32_LogicalShareAccess class, its AccessMask and Trustee properties. I don’t test the binary to translate it into all the subatomic permissions granted. I’ve simplified it to display only the 3 “Read”,”Change” and “Full control” available both in the UI and the output of the ‘net share’ command.

The script below can run on a local computer and target remote computers as well (specified as an array, see help)
You can use credentials to access remote computers if required.
The script will work both on Windows 7 or Windows XP.

Enjoy 🙂

    

#Requires -Version 2.0

<#
 
.SYNOPSIS    
    Get share related info from WMI
 
.DESCRIPTION  
    Get share related info from WMI
 
.PARAMETER ComputerName
    Non mandatory parameter, array of computernames, the default value is "." which means localhost in WMI.
 
.PARAMETER ShareName
    Non mandatory parameter, array of share names, when not specified, all shares are returned like the net share command
    When a share name is specified, it will return the output of the 'net share sharename' command

.PARAMETER Credential
    Non mandatory parameter, credential used to contact remote computer

.NOTES    
    Name: Get-Share
    Author: Emin Atac
    DateCreated: 05/02/2012
 
.LINK    
    https://p0w3rsh3ll.wordpress.com
 
.EXAMPLE    
    .\Get-Share.ps1 
    Returns all the shares on the local computer with the same output as the 'net share' command
 
.EXAMPLE    
    .\Get-Share.ps1 -ShareName ADMIN$
    Get the properties of the ADMIN$ share with the same output as the 'net share ADMIN$' command
 
.EXAMPLE
    .\Get-Share.ps1 -ShareName test-share$ | Select-Object -ExpandProperty Permissions
    Get the permissions on the 'test-share$' in a more readable format by piping it into the select-object cmdlet
    The same can be achieved with:
    (.\Get-Share -sharename "test-share$").Permissions
 
.EXAMPLE    
    .\Get-Share.ps1 -ShareName test-share$ -ComputerName remotecomputer1,remotecomputer2
    Get the share properties of test-share$ on remote computers
 
.EXAMPLE
    $cred = (Get-Credential)
    .\Get-Share.ps1 -ShareName "test-share1","test-share2" -ComputerName remotecomputer1,remotecomputer2 -Credential $cred
    Get the share properties of 2 shares specified as an array on 2 remote computers specified as an array by using another credential
    NB: Users and Caching mode will be returned as "unknown".
 
#>
 
[CmdletBinding(SupportsShouldProcess=$True)]
param (
[parameter(Mandatory=$false,Position=0)][system.array]$ShareName=$null,
[parameter(Mandatory=$false,Position=1)][system.array]$ComputerName=".",
[parameter(Mandatory=$false,Position=2)][System.Management.Automation.PSCredential]$Credential = $null
)


function ConvertTo-NtAccount
{
<#
 
.SYNOPSIS    
    Translate a SID to its displayname
 
.DESCRIPTION  
    Translate a SID to its displayname
 
.PARAMETER Sid
    Provide a SID
 
.NOTES    
    Name: ConvertTo-NtAccount
    Author: thepowershellguy
 
.LINK    
    http://thepowershellguy.com/blogs/posh/archive/2007/01/23/powershell-converting-accountname-to-sid-and-vice-versa.aspx
 
.EXAMPLE
    ConvertTo-NtAccount S-1-1-0
    Convert a well-known SID to its displayname
 
#>
 
param(
[parameter(Mandatory=$true,Position=0)][system.string]$Sid = $null
)
 begin
 {
    $obj = new-object system.security.principal.securityidentifier($sid)
 }
 process
 {
    try
    {
        $obj.translate([system.security.principal.ntaccount])
    }
    catch
    {
        # To remove the silent fail, uncomment next line
        # $_
    }
 }
 end {}
}

# Handle the sharename parameter and build a unique WMI with all share names in the array
if (-not($sharename))
{
    $Query = "Select * FROM Win32_Share" 
} else {

    for ( $i=0 ; $i -lt $sharename.Count ; $i++)
    {
        if ($i -eq 0)
        {
            $Query = "Select * FROM Win32_Share WHERE " + "Name = '" + $sharename[$i] + "'"
        } else {
            $Query = $Query + " OR Name = '" + $sharename[$i] + "'"
        }
    } # end of for loop
}

# Build a hashtable for splatting
$otherparams = @{}
if ($credential)
{
    $otherparams += @{Credential = $Credential}
}

# Prepare global script variable
$consoleoutput = @()

foreach ($computer in $computername)
{
    try
    {
        $shares = @(Get-WmiObject -ComputerName $computer -Query $Query -ErrorAction Stop @otherparams)
    }
    catch
    {
        # WMI was unable to retrieve the information
        switch ($_)
        {
            {$_.Exception.ErrorCode -eq 0x800706ba} { $reason =  "Unavailable (offline, firewall)" }
            {$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $reason = "Access denied" }
            default { $reason  = $_.Exception.Message }
        }
        Write-Host -ForegroundColor Yellow  -Object "Failed to connect to WMI on $Computer because: $reason"
        # break
    }

    # If we have shares returned by the above WMI query
    if ($shares.Count -ne 0)
    {
        # Loop
        foreach ($shareitem in $shares)
        {
            # Get the Maximum users properties
            if ($shareitem.AllowMaximum -eq $true)
            {
                $Max = "No limit"
            } else {
                $Max = $shareitem.MaximumAllowed
            }

            # Replace some special characters in the share name to use it as a regular expression pattern with the match operator
            $pattern = $shareitem.Name -replace "\$","\$"
            $pattern = $pattern -replace "\-","\-"
            $pattern = $pattern -replace "\.","\."

            # Get connected users to the share
            $Users = @()
            $shareconnections = @(Get-WmiObject -Class Win32_ConnectionShare -ComputerName  $computer -ErrorAction SilentlyContinue @otherparams)
            if ($shareconnections.Count -ne 0)
            {
                if (-not($credential))
                {
                    ForEach ($share in $shareconnections)
                    {
                        $allconnectedusers = @([wmi]$share.Dependent | Where-Object {$_.ShareName -match $pattern})
                        foreach ($user in $allconnectedusers)
                        {
                            $Users += (New-Object -TypeName PSObject -Property @{Username = $User.Username})
                        }
                    } # end of foreach
                } else {
                    ForEach ($share in $shareconnections)
                    {
                        if ($share.Dependent -match $pattern)
                        {
                            $Users += (New-Object -TypeName PSObject -Property @{Username = $(($share.Dependent  -split "=")[-1] -replace "`"","")})
                        }
                    } # end of foreach
                }
            }

            # Get the caching mode of the share

            # Define the  HKLM Constant and the Key we are looking for
            $HKLM = 2147483650
            $Key = "SYSTEM\CurrentControlSet\services\LanmanServer\Shares"

            $result = Invoke-WmiMethod -Path "ROOT\DEFAULT:StdRegProv" -ComputerName $computer -Name GetMultiStringValue -ArgumentList $HKLM,$Key,$($shareitem.Name)  -ErrorAction SilentlyContinue @otherparams
            
            if ($result.sValue -ne $null)
            {
                # We split the multiline string and just look for the CSCFlags string
                $CSCFlag = $null
                $CSCFlag = ((($result.svalue -split "`n") | Where-Object {$_ -match "^CSCFlags"}) -split "=")[1]
                
                switch($CSCFlag)
                {
                    0  {$Caching = "Manual caching of documents"}
                    16 {$Caching = "Automatic caching of documents"}
                    32 {$Caching = "Automatic caching of programs and documents"}
                    48 {$Caching = "Caching disabled"}
                    default {$Caching = "Unknown"}
                }
            } else {
                    # If we don't find it in the registry, it means it's the default value
                    $Caching = "Manual caching of documents"
            }
           
            # Get Permissions of the share

            # Change a little bit our pattern
            $pattern = $pattern + "`"$"

            # Reset array
            $Permissions = @()
                    
            # Now get the permissions for our share
            $shareperms = @(Get-WmiObject -Class Win32_LogicalShareAccess -ComputerName  $computer -ErrorAction SilentlyContinue @otherparams| Where-Object {$_.SecuritySetting -match $pattern})
                    
            # If we don't find it in the class, it means it's the default "everyone, full control"
            if ($shareperms.Count -eq 0)
            {
                    $Permissions = New-Object -TypeName PSObject -Property @{            
                            Username = "Everyone" 
                            AccessMask = "Full Control"
                    }
            } else {
                # Loop foreach permission returned by the WMI query of the Win32_LogicalShareAccess class
                ForEach ($perm in $shareperms)
                {
                    # Convert the SID property to a username
                    $Username = $Permission = $null
                    $Username = ConvertTo-NtAccount -SID (($perm.Trustee -split "=")[1] -replace "`"","")

                    # Change the binary to a human readable string
                    switch($perm.AccessMask)
                    {
                        1179817 { $Mask = "Read"}
                        1245631 { $Mask = "Change"}
                        2032127 { $Mask = "Full Control"}
                        default { $Mask = "Unknown"}
                    }

                    # Add to a custom object
                    $Permission = New-Object -TypeName PSObject -Property @{            
                            Username = $Username
                            AccessMask = $Mask
                    }

                    # Add the permission to our permissions array
                    $Permissions += $Permission

                } # end of foreach shareperms
            } # end of if shareperms.count

            # Display diffently the share name on a remote computer
            $formattedshare = $null
            if ($computer -ne ".")
            {
                $formattedshare = "\\$Computer\" + $shareitem.Name
            } else {
                $formattedshare = $shareitem.Name
            }

            # Build a final share object with all its properties
            $ShareObject = $null
            $ShareObject = New-Object -TypeName PSObject -Property @{
                "Share name" = $formattedshare
                Path = $shareitem.Path
                Remark = $shareitem.Description
                "Maximum Users" = $Max
                Users = $Users
                Caching = $Caching
                Permissions = $Permissions
                }

            # We can return it now, if we aren't listing all shares
            if (-not($sharename))
            {
                # Format the console output to display the same properties as the 'net share' command
                $consoleoutput += $ShareObject 
            } else {
                # Output the object with all its properties
                Write-Output -InputObject $ShareObject                
            }
        } # end of foreach shares

        # Console output that lists all shares
        if (-not($sharename))
        {
            Write-Output -InputObject $consoleoutput | Select-Object -Property "Share Name",Path,Remark
        }
    } else {
        Write-Host -ForegroundColor Yellow "$sharename share name not found on computer $computer"
    } # end of if shares
} # end of foreach $computername

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s