1

Our environment is in the process of removing a bunch of Windows 2003 servers, and in an attempt to pull permissions on shares that exist, I've been trying to come up with a solution to script it out.

The kicker to this however is that WMI is not working on these servers, so I cannot do a GWMI Win32_Share. Given the age of the servers as well, more modern powershell commands do not work.

What I can do, is a Get-ACL, however I need to provide the paths for the shares. The only way I can see to getting the full paths for the shares is through Net Share or a Get-ItemProperty HKLM , of which I've had no luck.

If anyone smarter than I out there knows a way to parse out just the "resources" column of a net share, you'd be doing me a huge favor.

Kjbojang1es
  • 47
  • 2
  • 8

2 Answers2

1

You can probably achieve the desired result with a bit of regex

Powershell V1 compatible

switch -regex (net share){
    '^(\S+)\s+(\w:.+?)(?=\s{2,})' {
        New-Object PSObject -Property @{
            ShareName = $matches.1
            Resource  = $matches.2
        }
    }
}

Powershell 3.0+

switch -regex (net share){
    '^(\S+)\s+(\w:.+?)(?=\s{2,})' {
        [PSCustomObject]@{
            ShareName = $matches.1
            Resource  = $matches.2
        }
    }
}

On powershell 5.1 you can use ConvertFrom-String with "training" data. It can be real sample data or generic. It may take some adjusting for your specific environment but this worked well in testing.

$template = @'
{Share*:ADMIN$}       {Path: C:\Windows}                      {Note:Remote Admin}
{Share*:Admin}        {Path: E:\Admin}                        {Note:Default share}
{Share*:Apps}         {Path: D:\Apps}                         {Note:test}
'@

net share | Select-Object -Skip 4 | Select-Object -SkipLast 2 |
    ConvertFrom-String -TemplateContent $template -OutVariable ShareList

Any output shown should now be contained in the variable $ShareList

$Sharelist | Get-Member -MemberType NoteProperty


   TypeName: System.Management.Automation.PSCustomObject

Name  MemberType   Definition              
----  ----------   ----------              
Note  NoteProperty string Note=Remote Admin
Path  NoteProperty string Path=C:\Windows  
Share NoteProperty string Share=ADMIN$ 

You could also use psexec to get the information remotely and apply either proposed solution.

switch -regex (.\PsExec.exe -nobanner \\$RemoteComputer cmd "/c net share"){
    '^(\S+)\s+(\w:.+?)(?=\s{2,})' {
        [PSCustomObject]@{
            ShareName = $matches.1
            Resource  = $matches.2
        }
    }
}

or

$template = @'
{Share*:ADMIN$}       {Path: C:\Windows}                      {Note:Remote Admin}
{Share*:Admin}        {Path: E:\Admin}                        {Note:Default share}
{Share*:Apps}         {Path: D:\Apps}                         {Note:Test}
'@

.\PsExec.exe -nobanner \\$RemoteComputer cmd "/c net share" |
    Select-Object -Skip 4 | Select-Object -SkipLast 2 |
        ConvertFrom-String -TemplateContent $template -OutVariable ShareList
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • Hey Doug, unfortunately these servers do not have powershell 5.1. The most up-to-date version that any of these have is 3.0 – Kjbojang1es Jul 15 '21 at 23:48
  • Hey Doug, I ended up going the regex route locally on the machine. However, due to the machines only having powershell v1.0 on them, had to re-do the hashtable PSObject a bit. I am however running into an issue with the trimming of the net share resources field. It looks like Paths with spaces at the end are being trimmed. For instance, a share with path: d:\customer\Care and Billing is being trimmed to d:\customer\Care and therefor, the get-acl is unable to run properly. I assume it has something to do with Trim logic: '^(\S+)\s+(\w:.+?)(?=\s+)' – Kjbojang1es Jul 16 '21 at 13:00
  • 1
    Change the last bit to (?=\s{2,}) and/or query remote from a decent version of powershell – Doug Maurer Jul 16 '21 at 13:20
  • @Kjbojang1es I also added a v1 compatible version, which is probably what you already did. Please consider marking this answer as the accepted solution. If a better solution comes along you can always change it. – Doug Maurer Jul 16 '21 at 14:04
  • As elegant as your `switch` statement is, @Doug, it isn't robust, unfortunately: The problem with trying to parse the output from a single call to `net share` alone is that values that are too long may be *truncated* (with `...`) or cause line-wrapping; that is, you cannot robustly get the full values. – mklement0 Jul 16 '21 at 19:53
  • As for `ConvertFrom-String`: Here's my standard advice: [`ConvertFrom-String`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertfrom-string) provides separator-based parsing as well as heuristics-based parsing based on templates containing example values. The separator-based parsing applies automatic type conversions you cannot control, and the template language is poorly documented, with the exact behavior hard to predict - it's best to avoid this cmdlet altogether. Also note that it is no longer available in _PowerShell (Core)_ (v6+). – mklement0 Jul 16 '21 at 20:00
  • 1
    Yeah it’s a best effort with OPs restrictions. – Doug Maurer Jul 16 '21 at 20:12
0

Unfortunately, robust parsing of net share output is non-trivial and requires a two-pass approach:

  • Collect all share names only from the output of net share.
  • For each share name $shareName, call net share $shareName and parse the share-specific properties into a custom object.

The problem with trying to parse the output from net share only is that values that are too long may be truncated (with ...) or cause line-wrapping; that is, you cannot robustly get the full values; by contrast, net share $shareName uses a list format in which the values are not subject to truncation.

The code below implements the two-pass approach:

Note:

  • I've verified that it works in PowerShell v2; I don't have access to v1 anymore.

  • As per the output from net share $shareName, the property name representing the local target path is Path, not Resource as in the output from net share.

  • If you run without elevation (not as admin), the following properties cannot be queried due to lack of permissions: Users, Caching, Permission; the code quietly ignores them.

# Run `net share` and collect its output lines in an array.
$lines = net share

# First pass: 
# Extract all share *names* from the lines.
# Note: Assumes that 4 header rows and a trailing status row can be ignored.
$shareNames = switch -regex ($lines[4..($lines.Count-2)]) { 
   # Extract the first whitespace-separated token,
   # but ignore wrapped lines (for overly long names / paths), which
   # start with whitespace.
   '^\S' { (-split $_)[0] }
}

# Second pass:
# Call `net share <name>` for each name and parse the list of properties into
# a [pscustomobject] instance.
# Note: 
#  * When runnning *without elevation*, querying the following fields 
#    fails due to lack of permissions, which is quietly ignored here:
#      Users, Caching, Permission
#  * Assumes that property names are either one or two words and followed
#    by at least two spaces.
foreach ($shareName in $shareNames) {
  Write-Verbose -vb "Processing $shareName..."
  # Initialize a hashtable to store the properties.
  # Note: In PSv3+, you can use [ordered] @{} to define the properties in predictable order.
  $properties = @{}
  # Parse the property lines and add them to the hashtable.
  switch -regex (net share $shareName 2>$null) {
     '^(\w+ (?:\w+)?) {2,}(.*)' {
        $properties[$Matches[1]] = ($Matches[2]).Trim()
     }
  }
  # Convert the hashtable to a custom object.
  # Note: In PSv3+, you can simplify to [pscustomobject] $properties
  New-Object pscustomobject $properties
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
mklement0
  • 382,024
  • 64
  • 607
  • 775