File Server Management with Windows PowerShell

Make a tedious task easier, without a lot of scripting.

Jeffery Hicks

January 23, 2013

15 Min Read
the word powershell on an abstract code background
Alamy

Managing file servers can be a tedious and thankless task for many IT pros. But it doesn't need to be that way. By incorporating Windows PowerShell, you can easily get a handle on shared file resources -- and maybe even have a little fun. And none of it really requires any scripting. Everything I want to show you should take no more than a few interactive commands. Of course, if you find yourself running these commands often, then turning them into a script or function is the next logical step.

The beauty of PowerShell is that you can take the results of the commands that I'm going to demonstrate and do whatever you want. Need to save the results to a comma-separated value (CSV) file? Pipe them to Export-CSV. Need an HTML report? Use ConvertTo-HTML. And everything I'm going to show you scales; if you can use a command for one computer, you can use it for 10, 100, or 1,000 systems.

First, let me show you what you can do to manage what you have in your file shares today. Then we'll look at provisioning file sharing.

Get File Shares

Let's begin by identifying what's being shared. This task is easy: Simply query the Win32_Share class to use Windows Management Instrumentation (WMI). You don't even need to be logged on to the file server. You can run this command from the comfort of your cubicle:

Related:How To Visualize SQL Server Data in PowerShell (With Sample Script)

Get-WmiObject -class -Win32_Share -computername MyFile 

Now, when you run this command, you'll get all shares, including printers (if any). Because we're talking about file shares, let's limit the query. All Win32_Share instances have a Type property, as Table 1 shows. Thus, to limit the search, we can add a filter to our original command:

Get-WmiObject -class -Win32_Share -computername MyFile -filter"Type=0" 

This approach gets rid of the administrative shares. You can see an example in Figure 1.

Figure 1: Listing Non-Administrative Shares with WMI 

But if you're looking for other hidden shares -- that is, those that end in a dollar sign ($) -- all you need is a slight tweak to the filter:

Get-WmiObject -Class win32_share -computername MyFile -filter"Type=0 AND name like '%$'" 

In WMI, the percent character (%) is used as a wildcard. Returning all shares except those that are hidden is a little trickier. You'll need to use a compound comparison using a wildcard: 

Get-WmiObject -Class win32_share -computername MyFile -filter"type=0 AND name like '%[^$]'" 

 This command returns all Win32_Share objects that have a Type property of 0 and a name that doesn't end in $.

Get Folder Size

A typical task that's probably on your plate is creating reports about how much disk space a folder is consuming. The quick approach is to simply use Get-ChildItem, or its alias dir, and pipe the results to Measure-Object:

dir c:sharespublic -recurse | where {-Not $_.PSIsContainer}|Measure-Object -Property length -Sum -Minimum -Maximum 

You'll end up with a measurement object that shows the total number of objects, the total size in bytes, and the smallest and largest file sizes. In the previous command, I've filtered out folders. PowerShell 3.0 has better ways of doing this, but the command that I've used works in both PowerShell 2.0 and 3.0. This is the type of command that is best run locally (a great reason to use PowerShell remoting). The code in Listing 1 combines this command with our WMI technique to get a size report for top-level folders. You can format or process $results any way you like. How about an easy-to-read table? Just use this command:

Related:How To Use PowerShell for Automated Event Response

$results | Format-Table Computername,Fullname,SizeKB,NumberFiles -autosize 

Figure 2 illustrates the output that you can expect.
Figure 2: Easy-to-Read Output 

It doesn't take much more effort to build a comprehensive usage report for all shares on a file server. I'll save you the time: Take a look at Listing 2. Again, I can slice and dice $results any way I need. Figure 3 shows one approach.
Figure 3: Usage Report for File Server Shares 

Get Files by Owner

A variation on this theme is to find file usage by owner. If you use quotas, you most likely already have reporting in place. Otherwise, all you need to do is retrieve the file ACL, which includes the owner, and aggregate the results. I find that the best approach is to add the owner as a custom property:

$data=dir | where {-not $_.PSIsContainer} |select name, @{Name="Owner";Expression={(Get-ACL $_.fullname).Owner}}, length 

We can group this output by the new owner property, and then process the new object: 

Related:Building Graphical PowerShell Tools: A Three-Part Guide

$data | group owner | Select Name,Count,@{Name="Size";Expression={($_.Group | Measure-Object -Property Length -sum).Sum}} 

With just a little effort, you can apply this approach to a file share, as the code in Listing 3 does. I should also point out that you might run into issues if filename paths are longer than 260 characters or if filenames contain any odd characters, especially if you attempt to run Get-ACL. In PowerShell 3.0, this cmdlet supports -LiteralPath, which helps.

I'll admit that some of these examples are getting to be a bit much to type -- and they aren't the only way to accomplish these tasks, but the point is that you can. You can also get by with less detail and structure for ad hoc style reporting. Figure 4 illustrates this code sample with the results formatted as an easy-to-read table.
Figure 4: Ad Hoc Reporting 

Get Files by Age

The last reporting technique that I want to demonstrate is building a file aging report. Actually, what we're creating is a collection of objects that we can re-use in several ways. You might want to use the objects to delete or move files, or you might want to build a report that can be emailed to management. Always construct PowerShell commands with maximum reuse and flexibility in mind.

Capturing file aging is a tricky thing. In PowerShell, the file object has several properties that you might want to use. For example,

get-item c:workwishlist.txt | Format-List Name,*time 

produces the output that Figure 5 shows.

Figure 5: File Aging Data 

Personally, I find it best to use LastWriteTime to indicate when a file was last touched. I've seen situations in which LastAccessTime is updated through third-party tools such as virus scanners, which can lead to erroneous conclusions. And LastAccessTime has been disabled by default since the days of Windows Vista, although you can re-enable it. You also need to be careful because these values can change depending on whether you're copying or moving a file between volumes. But you can decide for yourself. Using this file as an example, we can have PowerShell tell us how old the file is, as Listing 4 shows.

The Age property is a TimeSpan object, and the Days property is merely the TotalDays property of that object. But, because we can do this for a single file, we can do it for all files. Let's look at my public share and find all the files that haven't been modified in 400 days.

dir c:sharespublic -recurse |Select Fullname,CreationTime,LastWriteTime,@{Name="Age";Expression={(Get-Date)-$_.LastWriteTime}},@{Name="Days";Expression={[int]((Get-Date)-$_.LastWriteTime).TotalDays}},@{Name="Owner";Expression={(Get-ACL $_.fullname).Owner}} |Where {$_.Days -ge 400} | Sort Days -Descending 

I went ahead and included the file owner. Figure 6 shows the results from running this code in a remote session on my file server.

Figure 6: Running Code in a Remote Session

I could save these results to a variable and reuse them however I want. Because I have the full filename, piping the variable to a command such as Remove-Item wouldn't be too difficult.

Another approach to file aging is to build a report, or object collection, based on file age buckets. A little more effort is involved; after we calculate or determine the age element, we need to add some logic to do something with it.

One of my favorite techniques is to find out how many files were last modified, by year. Again, I'll use the interactive remoting session on my file server to demonstrate: 

dir c:sharessales -recurse |Select Fullname,LastWriteTime,@{Name="Age";Expression={(Get-Date)-$_.LastWriteTime}},@{Name="Year";Expression={$_.LastWriteTime.Year}} |Group-Object Year | Sort Name 

As you can see in Figure 7, it looks as though some cleanup is in order. If I need more detail, I can always analyze the Group property, which is the collection of files.

Figure 7: Discovering Modified Files by Year 

Finally, what about aging buckets? It might be useful to know how many files haven't been modified in 30, 90, or 180 days. Unfortunately, there isn't an easy way to use Group-Object for this, so I need to take a more brute-force approach; take a look at Listing 5. Figure 8 shows the result when I run this code against my scripts folder, which I know has a decent age distribution. My code doesn't include the actual files, but it wouldn't be too difficult to modify my example.

Figure 8: File Modification Ages 

Creating File Shares

Next, let's look at how we can use PowerShell to create and manage files and shares. Everything I've demonstrated up to now can work in both PowerShell 2.0 and 3.0 (although in PowerShell 3.0, you could simplify my examples in a few places). File server management in PowerShell 2.0 requires WMI and complicated scripting. But in PowerShell 3.0, especially if you have Windows Server 2012, this type of management is a thing of beauty. I'm going to switch gears and manage a Windows Server 2012 file server from a Windows 8 desktop so that I can take advantage of some new features.

Everything we want is bundled into the SMBShare module, which is installed on Windows 8 by default. The commands in this module let us easily manage file shares locally or remotely. I'm not going to cover every command, but they all follow a similar format and I strongly encourage you to read the Help and examples. We'll begin by using the New-SMBShare command to create a new file share.

We need to specify a file path, and unlike what happens when we use the GUI, the command doesn't create the folder if it doesn't exist. So we'll create one and set the NTFS permissions. Doing so takes several steps. Since they must be done on the remote server, I'll set up a PowerShell remoting session:

$session=New-PSSession -ComputerName SRV2K12RC 

I could use this session interactively, but I'm going to run the commands by using Invoke-Command, which is preferable when you're building an automated process. First, I'll create the new folder:

invoke-command -ScriptBlock {mkdir c:sharescompanyfiles}-Session $session 

Now for the tricky part. I want to set the NTFS permissions so that JDHLABDomain Users have Change permission. This goal requires creating a new access rule, modifying the access rule list, and re-applying it to the folder. I'll put the commands in the scriptblock that Listing 6 shows. I wrote this scriptblock so that it takes a parameter for the path, which makes it re-usable:

Invoke-Command -ScriptBlock $sb -Session $session -ArgumentListc:sharescompanyfiles 

There are ways to streamline this process, but I kept the steps distinct for the sake of clarity. Now we're ready to create the share. 

I could use the session, but I want to demonstrate how you might use the New-SmbShare command to remotely connect to a file server:

New-SmbShare -Name Files -Path c:sharescompanyfiles-CimSession SRV2K12RC -FullAccess "jdhlabdomain admins"-ChangeAccess Everyone -Description "Company files" 

The default share-access permission is ReadOnly. I've granted domain admins Full Control on the share, and everyone else has the Change permission. The path is relative to the remote computer, which must be running PowerShell 3.0.

Advanced Share Settings

We can look at our share any time by using the Get-SMBShare command, as you can see in Figure 9. We can set a few extras with our file shares, such as whether to encrypt the SMB connection, which type of folder enumeration mode to use, and which type of caching mode to use. I'm going to use Set-SMBShare to fine-tune the share that I just created, as Listing 7 shows, because I neglected to define these properties during the share's creation.
Figure 9: Using Get-SMBShare to Review a Share 

I applied this change to a single file share, but it would have been just as easy to use Get-SMBShare to retrieve all file shares and then pipe them to Set-SMBShare and apply a change to all of them:

Get-SMBShare -CimSession SRV2K12RC -Special $False |Set-SmbShare -EncryptData $True -Confirm:$false 

This command retrieved all the shares (except administrative shares) on SRV2K12RC and set the EncryptData property to True. I didn't want to answer a prompt for each share, so I set the -Confirm switch to False. Set-SMBshare won't write anything to the pipeline unless you use -Passthru. But I was able to modify everything with one simple command.

Removing Shares

The last thing to look at is removing a share. The code in Listing 8 stops sharing the share that I just created. Could it get any easier? Of course, the folder structure is still in place on the file server.

Putting It All Together

Let's wrap this up by putting all the management pieces together in one place: a PowerShell workflow. I don't have space to cover this terrific addition to PowerShell or to go into my code sample, but the great thing about a workflow is that I can have some commands run in parallel. For example, after the folder is created, I can create the share and set the NTFS permissions at the same time, as Listing 9 shows.

This workflow creates a new folder and file share, assigning permissions to a user or group. I can execute this from my Windows 8 desktop and have it run on my Windows Server 2012 file server by using the following command (which should be entered on one line).

New-FileShare -Name adeco`-Path c:sharesadeco `-Principal jdhlabadeco `-Right "FullControl" `-PSComputerName SRV2K12RC 

The process takes only a few seconds. You can see the results in Figure 10.

Figure 10: Creating File Shares with a PowerShell Workflow 

There's nothing wrong with using the GUI to manage your file servers. But when you're looking for specialized reporting or provisioning automation, PowerShell is your best bet.

You can download a copy of my code samples. If you have questions about any of these examples (or other PowerShell issues), use the forums at PowerShell.org

Listing 1: Getting a Size Report for Top-Level Folders

$share=Get-WmiObject -Class Win32_Share -ComputerName  CHI-FP01 -filter "name='Sales'"$sb={Param ($path)dir $path | where {$_.PSIscontainer} |foreach {  $stats=dir $_.Fullname -recurse -errorAction  "SilentlyContinue" | where {-NOT $_.PSIscontainer} |  Measure-object -Property Length -sum  New-Object -TypeName PSObject -Property @{    Computername=$env:Computername    Path=$_.Name    Fullname=$_.Fullname    SizeKB=[math]::Round(($stats.sum/1KB),2)    NumberFiles=$stats.count  } #property  } #foreach} #sb $results=Invoke-Command -ScriptBlock $sb -ComputerName  $share.__SERVER -ArgumentList @($share.path)  -HideComputerName 

Listing 2: Building a Usage Report for File Server Shares

$sb={#Get the file shares locally.$shares=Get-WmiObject -Class Win32_Share -filter "type=0"foreach ($share in $shares) {    #Suppress any access denied error messages.    Write-Host "Measuring $($share.path)" -ForegroundColor Green    $stats=dir $share.path -Recurse -ErrorAction SilentlyContinue |      Where {-Not $_.PSIscontainer} |      Measure-Object -Property Length -Sum      $hash=@{      Computername=$env:Computername      Path=$share.path      Share=$share.Name      SizeKB=[math]::Round(($stats.sum/1KB),2)      Files=$stats.count      }      #Write a custom object to the pipeline for each share.      New-Object -TypeName PSObject -Property $hash    } #foreach $share}#sb$results = Invoke-Command -ScriptBlock $sb -ComputerName  CHI-FP01 -HideComputerName

Listing 3: Grouping Output by Owner Property

$sb={Param($path)$data=dir $path |  where {-not $_.PSIsContainer} |  select name, @{Name="Owner";Expression=  {(Get-ACL $_.fullname).Owner}},length  $data | group -property owner |  Select @{Name="Computername";Expression={$env:computername}},  @{Name="Path";Expression={$path}},Name,Count,@{Name=  "Size";Expression={  ($_.Group | Measure-Object -Property Length -sum).Sum}}} #sb <#Run the command remotely and suppress the RunspaceID since wedon't really need it.#>Invoke-Command -ScriptBlock $sb -ComputerName CHI-FP01  -ArgumentList @("c:sharespublic") -HideComputerName |  Select * -ExcludeProperty RunspaceID

Listing 4: Determining File Age

PS C:work> get-item wishlist.txt | format-list name,  CreationTime,LastWriteTime,>> @{Name="Age";Expression={(Get-Date)-$_.LastWriteTime}},>> @{Name="Days";Expression={((Get-Date)  -$_.LastWriteTime).TotalDays}}>>  Name          : wishlist.txtCreationTime  : 11/23/2010 10:31:10 PMLastWriteTime : 2/15/2011 7:36:34 AMAge           : 511.06:42:17.4251748Days          : 511.279372271039 

Listing 5: Determining File Modification Ages

$path="c:scripts" #Get some other properties in case we want to further#break down each bucket.$files=dir $path -recurse |Select Fullname,CreationTime,LastWriteTime,Length,@{Name="Age";Expression={(Get-Date)-$_.LastWriteTime}},@{Name="Days";Expression={[int]((Get-Date)  -$_.LastWriteTime).TotalDays}} $hash=@{Path=$pathOver=      ($files | Where {$_.Days -gt 365} | Measure-Object).Count'365Days'= ($files | Where {$_.Days -gt 180 -AND  $_.Days -le 365} | Measure-Object).Count'180Days'= ($files | Where {$_.Days -gt 90 -AND  $_.Days -le 180} | Measure-Object).Count'90Days'=  ($files | Where {$_.Days -gt 30 -AND  $_.Days -le 90} | Measure-Object).Count'30Days'=  ($files | Where {$_.Days -gt 7 -AND  $_.Days -le 30} | Measure-Object).Count'7Days'=   ($files | Where {$_.Days -gt 0 -AND  $_.Days -le 7} | Measure-Object).Count}New-Object -TypeName PSObject -Property $hash |Select Path,Over,365Days,180Days,90Days,30Days,7Days 

Listing 6: Creating, Modifying, and Applying an Access Rule

$sb={Param($path)$du=new-object System.Security.AccessControl.FileSystem  AccessRule "jdhlabdomain users","Modify","allow"$acl = Get-ACL $path$acl.AddAccessRule($du)Set-Acl -Path $path -AclObject $acl}

Listing 7: Fine-Tuning a Share

PS C:> Set-SmbShare -Name Files -EncryptData $True  -FolderEnumerationMode AccessBased -CachingMode  Documents -CimSession SRV2K12RC ConfirmAre you sure you want to perform this action?SRV2K12RC: Performing operation 'Modify' on Target '*,Files'.[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?]  Help (default is "Y"):

Listing 8: Stopping a File Share

PS C:> Remove-SmbShare -Name Files -CimSession SRV2K12RC ConfirmAre you sure you want to perform this action?SRV2K12RC: Performing operation 'Remove-Share' on Target  '*,Files'.[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?]  Help (default is "Y"): 

Listing 9: Creating a Share and Setting NTFS Permissions

Workflow New-FileShare {Param([string]$Name,[string]$Path,[string]$Principal,[string]$Right="Modify") #This must be done first.Sequence {    #create the folder    Write-Verbose -Message  "Creating new folder $path on      $pscomputername"    $newfolder = New-Item -Path $path -ItemType Directory} #Then this step can happen.Sequence {    Parallel {        #these commands can happen in parallel        InlineScript {            Write-Verbose -Message "Modifying NTFS permissions"            Write-Verbose -Message "Creating entry for              $using:principal with a right of $using:Right"            $entry=New-Object -typename System.Security              .AccessControl.FileSystemAccessRule -argumentlist              $using:Principal,$using:Right,"allow"            #get the existing ACL            $acl = Get-ACL -path $using:path            #add the new entry            $acl.AddAccessRule($entry)            Write-Verbose -Message "Applying the new ACL"            Set-Acl -Path $using:path -AclObject $acl        } #inline         #Create the share.        Write-Verbose -message "Creating the file share $name"        $newshare = New-SmbShare -Name $name -Path $path          -Description "File share for $principal" -EncryptData $True          -FolderEnumerationMode AccessBased -CachingMode          Documents -FullAccess "$env:userdomaindomain admins"          -ChangeAccess $Principal    } #Parallel} #sequence #Get results.Sequence {    Parallel {        Write-Verbose -Message "Getting the new share"        Get-SmbShare -Name $name        Write-Verbose -Message "Getting the new share access"        Get-SmbShareAccess -Name $name    }}
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like