A blog by Jose Barreto, a member of the File Server team at Microsoft.
All messages posted to this blog are provided "AS IS" with no warranties, and confer no rights.
Information on unreleased products are subject to change without notice.
Dates related to unreleased products are estimates and are subject to change without notice.
The content of this site are personal opinions and might not represent the Microsoft Corporation view.
The information contained in this blog represents my view on the issues discussed as of the date of publication.
You should not consider older, out-of-date posts to reflect my current thoughts and opinions.
© Copyright 2004-2012 by Jose Barreto. All rights reserved.
Follow @josebarreto on Twitter for updates on new blog posts.
Overview
In a previous blog post, I have examined some of PowerShell’s control structures and included an example gathered some information from web. If you haven’t seen it, you can check it at http://blogs.technet.com/josebda/archive/2010/04/04/experimenting-with-powershell-v2-scripting-variables-and-control-structures.aspx. At the end of that post, I suggested a project to check free spaces on file servers. That’s what this post tackles
The goal here is to remotely check a file server, enumerate the shares on it and check the free space on the volume behind that share. For these tasks, we’ll use the WMI providers for file shares (Win32_Share), logical disks (Win32_LogicalDisk) and volumes (Win32_Volume).
Looking at your shares
To start, let’s do something simple. We’ll use the Get-WMIObject (GWMI for short) and the Win32_Share WMI class to obtain a list of all the shares in a server. The –ComputerName parameter is used to specify the remote server.
Get-WmiObject Win32_Share -ComputerName josebda-s1 | Select Name, Path, Type | FT Name Path Type---- ---- ----ADMIN$ C:\Windows 2147483648C$ C:\ 2147483648E$ E:\ 2147483648IPC$ 2147483651josebda-dfs C:\DFSRoots\josebda-dfs 0Proposals2009 C:\Proposals\Proposals2009 0Proposals2010 C:\Proposals\Proposals2010 0Software C:\Software 0StorageReports C:\StorageReports 0
Get-WmiObject Win32_Share -ComputerName josebda-s1 | Select Name, Path, Type | FT
Name Path Type---- ---- ----ADMIN$ C:\Windows 2147483648C$ C:\ 2147483648E$ E:\ 2147483648IPC$ 2147483651josebda-dfs C:\DFSRoots\josebda-dfs 0Proposals2009 C:\Proposals\Proposals2009 0Proposals2010 C:\Proposals\Proposals2010 0Software C:\Software 0StorageReports C:\StorageReports 0
Looking at your logical disks
Next, we’ll look at the logical disks at that same server. The command is similar, obviously with a different WMI Class.
Get-WmiObject Win32_LogicalDisk -ComputerName josebda-s1 | Select Name, Size, FreeSpace Name Size FreeSpace---- ---- ---------C: 239213735936 211060846592D: 2996799488 0
Get-WmiObject Win32_LogicalDisk -ComputerName josebda-s1 | Select Name, Size, FreeSpace
Name Size FreeSpace---- ---- ---------C: 239213735936 211060846592D: 2996799488 0
Script to cross-reference shares and drives
Now that we know where to find all the information, we can put together a script to put it all together. For this one, we’ll skip the hidden administrative shares (filtering for shares with type 0, which are regular file shares). I’ll also use a little trick to create a new object with a custom set of properties, that mix share and logical disk information. Here’s that script:
$Shares = Get-WmiObject Win32_Share -ComputerName josebda-s1Foreach ($Share in $Shares){ If ($Share.Type -eq 0) { $Result = "" | Select Server, Share, Path, Drive, Used, Free, Total $Result.Server=$Share.__SERVER $Result.Share=$Share.Name $Result.Path=$Share.Path $Result.Drive=$Result.Path[0]; $Filter = "Name ='"+$Result.Drive+":'" $Drive = Get-WMIObject Win32_LogicalDisk -Filter $Filter -ComputerName $Result.Server $Result.Total=$Drive.Size $Result.Free=$Drive.FreeSpace $Result.Used=$Drive.Size - $Drive.FreeSpace $Result }}
The script has a couple of issues. First, we assume that the first letter of the path behind a share can be used to find the matching logical disk. Also, if we have several shares on the same drive, we query WMI multiple times to get the drive information. But we'll overlook that for now.
Output of the script
Once you run it (the easiest way is to use the “Windows PowerShell ISE”), you will get the following output:
Server : JOSEBDA-S1Share : josebda-dfsPath : C:\DFSRoots\josebda-dfsDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936 Server : JOSEBDA-S1Share : Proposals2009Path : C:\Proposals\Proposals2009Drive : CUsed : 28151881728Free : 211061854208Total : 239213735936 Server : JOSEBDA-S1Share : Proposals2010Path : C:\Proposals\Proposals2010Drive : CUsed : 28151881728Free : 211061854208Total : 239213735936 Server : JOSEBDA-S1Share : SoftwarePath : C:\SoftwareDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936 Server : JOSEBDA-S1Share : StorageReportsPath : C:\StorageReportsDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Server : JOSEBDA-S1Share : josebda-dfsPath : C:\DFSRoots\josebda-dfsDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Server : JOSEBDA-S1Share : Proposals2009Path : C:\Proposals\Proposals2009Drive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Server : JOSEBDA-S1Share : Proposals2010Path : C:\Proposals\Proposals2010Drive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Server : JOSEBDA-S1Share : SoftwarePath : C:\SoftwareDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Server : JOSEBDA-S1Share : StorageReportsPath : C:\StorageReportsDrive : CUsed : 28151881728Free : 211061854208Total : 239213735936
Turning it into a one-liner
Note that, because we’re sending an object to the pipeline, you can do interesting things with it like SELECT just a few of the properties, find just share WHERE a certain condition is met, GROUP results or FORMAT the results as a TABLE. Another fun thing you can do is write this whole thing as a single command line (some people are into that :-). In that case, you might want to use shorter variable names and use abbreviated aliases for cmdlets and parameters. Here’s an example (that does exactly the same as the script above) that you can just cut and paste into a command line:
GWMI Win32_Share -CN josebda-s1 | % { If ($_.Type -eq 0) { $R="" | Select Server, Share, Path, Drive, Used, Free, Total; $R.Server=$_.__SERVER; $R.Share=$_.Name; $R.Path=$_.Path; $R.Drive=$R.Path[0]; $F="Name ='"+$R.Drive+":'"; $D=GWMI Win32_LogicalDisk -Filter $F -CN $R.Server; $R.Total=$D.Size; $R.Free=$D.FreeSpace; $R.Used=$D.Size-$D.FreeSpace; $R} } | FT Server Share Path Drive Used Free Total------ ----- ---- ----- ---- ---- -----JOSEBDA-S1 josebda-dfs C:\DFSRoots\j... C 28152012800 211061723136 239213735936JOSEBDA-S1 Proposals2009 C:\Proposals\... C 28152012800 211061723136 239213735936JOSEBDA-S1 Proposals2010 C:\Proposals\... C 28152012800 211061723136 239213735936JOSEBDA-S1 Software C:\Software C 28152012800 211061723136 239213735936JOSEBDA-S1 StorageReports C:\StorageRep... C 28152012800 211061723136 239213735936
GWMI Win32_Share -CN josebda-s1 | % { If ($_.Type -eq 0) { $R="" | Select Server, Share, Path, Drive, Used, Free, Total; $R.Server=$_.__SERVER; $R.Share=$_.Name; $R.Path=$_.Path; $R.Drive=$R.Path[0]; $F="Name ='"+$R.Drive+":'"; $D=GWMI Win32_LogicalDisk -Filter $F -CN $R.Server; $R.Total=$D.Size; $R.Free=$D.FreeSpace; $R.Used=$D.Size-$D.FreeSpace; $R} } | FT
Server Share Path Drive Used Free Total------ ----- ---- ----- ---- ---- -----JOSEBDA-S1 josebda-dfs C:\DFSRoots\j... C 28152012800 211061723136 239213735936JOSEBDA-S1 Proposals2009 C:\Proposals\... C 28152012800 211061723136 239213735936JOSEBDA-S1 Proposals2010 C:\Proposals\... C 28152012800 211061723136 239213735936JOSEBDA-S1 Software C:\Software C 28152012800 211061723136 239213735936JOSEBDA-S1 StorageReports C:\StorageRep... C 28152012800 211061723136 239213735936
Looking at volumes and mount points
In the previous scripts, there was at least one thing that we did not take into consideration. The folders used by the share could be under a mount point. You see, Windows allows you to mount a volume under an empty folder of an existing volume. To check this out, you could use the Win32_Volume WMI class:
Get-WmiObject Win32_Volume -ComputerName josebda-s1 | Select Name, Capacity, FreeSpace, BootVolume, SystemVolume, FileSystem | FT Name Capacity FreeSpace BootVolume SystemVolume FileSystem---- -------- --------- ---------- ------------ ----------\\?\Volume{3d9fc... 104853504 75362304 False True NTFSC:\Proposals\ 6441398272 6379917312 False False NTFSC:\ 239213735936 211063054336 True False NTFS\\?\Volume{150c7... 4294963200 4244283392 False False NTFSD:\ 2996799488 0 False False UDF
Get-WmiObject Win32_Volume -ComputerName josebda-s1 | Select Name, Capacity, FreeSpace, BootVolume, SystemVolume, FileSystem | FT
Name Capacity FreeSpace BootVolume SystemVolume FileSystem---- -------- --------- ---------- ------------ ----------\\?\Volume{3d9fc... 104853504 75362304 False True NTFSC:\Proposals\ 6441398272 6379917312 False False NTFSC:\ 239213735936 211063054336 True False NTFS\\?\Volume{150c7... 4294963200 4244283392 False False NTFSD:\ 2996799488 0 False False UDF
Along with a few volumes that are not mounted (first and fourth on the list), you can see that the “C:\Proposals” folder is actually a mount point. That is important because it won’t use space from the “C:\” volume.
Script to cross-reference shares and mount points
We now need to change our script to account for that. It’s a bit tricky because mount points look exactly like folders and could happen anywhere in the main volume hierarchy. One way to do it is taking one pass at the list of volumes for each share we process, verifying what the the longest path in the volumes list that matches the beginning of the file share path.
$Shares = Get-WMIObject Win32_Share -ComputerName Josebda-s1$Volumes = Get-WMIObject Win32_Volume -ComputerName Josebda-s1Foreach ($Share in $Shares) { If ($Share.Type -eq 0) { $Result = "" | Select Server, Share, Path, Volume, Used, Free, Total $Result.Server=$Share.__SERVER $Result.Share=$Share.Name $Result.Path=$Share.Path $ShareVolume="" | Select Name Foreach ($Volume in $Volumes) { If ( ($Result.Path.Length -ge $Volume.Name.Length) -and ($Volume.Name.Length -gt $ShareVolume.Name.Length) ) { If ($Result.Path.Substring(0,$Volume.Name.Length) -eq $Volume.Name) { $ShareVolume = $Volume } } } $Result.Volume=$ShareVolume.Name; $Result.Total=$ShareVolume.Capacity $Result.Free=$ShareVolume.FreeSpace $Result.Used=$ShareVolume.Capacity - $ShareVolume.FreeSpace $Result } }
Output of the updated script
Here’s what the output looks now:
Server : JOSEBDA-S1Share : josebda-dfsPath : C:\DFSRoots\josebda-dfsVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936 Server : JOSEBDA-S1Share : Proposals2009Path : C:\Proposals\Proposals2009Volume : C:\Proposals\Used : 61480960Free : 6379917312Total : 6441398272 Server : JOSEBDA-S1Share : Proposals2010Path : C:\Proposals\Proposals2010Volume : C:\Proposals\Used : 61480960Free : 6379917312Total : 6441398272 Server : JOSEBDA-S1Share : SoftwarePath : C:\SoftwareVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936 Server : JOSEBDA-S1Share : StorageReportsPath : C:\StorageReportsVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936
Server : JOSEBDA-S1Share : josebda-dfsPath : C:\DFSRoots\josebda-dfsVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936
Server : JOSEBDA-S1Share : Proposals2009Path : C:\Proposals\Proposals2009Volume : C:\Proposals\Used : 61480960Free : 6379917312Total : 6441398272
Server : JOSEBDA-S1Share : Proposals2010Path : C:\Proposals\Proposals2010Volume : C:\Proposals\Used : 61480960Free : 6379917312Total : 6441398272
Server : JOSEBDA-S1Share : SoftwarePath : C:\SoftwareVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936
Server : JOSEBDA-S1Share : StorageReportsPath : C:\StorageReportsVolume : C:\Used : 28152209408Free : 211061526528Total : 239213735936
Another one-liner, now with mount points
As you can see, we now correctly account for the free space for the shares under a mount point. Once again, you can try the “one liner” version below, with a format-table at the end.
$VV=GWMI Win32_Volume -CN Josebda-s1; GWMI Win32_Share -CN Josebda-s1 | % { If ($_.Type -eq 0) { $R=""|Select Server, Share, Path, Volume, Used, Free, Total; $R.Server=$_.__SERVER; $R.Share=$_.Name; $R.Path=$_.Path; $SV=""|Select Name; foreach($V in $VV) { If (($R.Path.Length -ge $V.Name.Length) -and ($V.Name.Length -gt $SV.Name.Length) ) { If ($R.Path.Substring(0,$V.Name.Length) -eq $V.Name) {$SV=$V} } } $R.Volume=$SV.Name; $R.Total=$SV.Capacity; $R.Free=$SV.FreeSpace; $R.Used=$SV.Capacity-$SV.FreeSpace; $R } } | FT Server Share Path Volume Used Free Total------ ----- ---- ------ ---- ---- -----JOSEBDA-S1 josebda-dfs C:\DFSRoots\j... C:\ 28152344576 211061391360 239213735936JOSEBDA-S1 Proposals2009 C:\Proposals\... C:\Proposals\ 61480960 6379917312 6441398272JOSEBDA-S1 Proposals2010 C:\Proposals\... C:\Proposals\ 61480960 6379917312 6441398272JOSEBDA-S1 Software C:\Software C:\ 28152344576 211061391360 239213735936JOSEBDA-S1 StorageReports C:\StorageRep... C:\ 28152344576 211061391360 239213735936
$VV=GWMI Win32_Volume -CN Josebda-s1; GWMI Win32_Share -CN Josebda-s1 | % { If ($_.Type -eq 0) { $R=""|Select Server, Share, Path, Volume, Used, Free, Total; $R.Server=$_.__SERVER; $R.Share=$_.Name; $R.Path=$_.Path; $SV=""|Select Name; foreach($V in $VV) { If (($R.Path.Length -ge $V.Name.Length) -and ($V.Name.Length -gt $SV.Name.Length) ) { If ($R.Path.Substring(0,$V.Name.Length) -eq $V.Name) {$SV=$V} } } $R.Volume=$SV.Name; $R.Total=$SV.Capacity; $R.Free=$SV.FreeSpace; $R.Used=$SV.Capacity-$SV.FreeSpace; $R } } | FT
Server Share Path Volume Used Free Total------ ----- ---- ------ ---- ---- -----JOSEBDA-S1 josebda-dfs C:\DFSRoots\j... C:\ 28152344576 211061391360 239213735936JOSEBDA-S1 Proposals2009 C:\Proposals\... C:\Proposals\ 61480960 6379917312 6441398272JOSEBDA-S1 Proposals2010 C:\Proposals\... C:\Proposals\ 61480960 6379917312 6441398272JOSEBDA-S1 Software C:\Software C:\ 28152344576 211061391360 239213735936JOSEBDA-S1 StorageReports C:\StorageRep... C:\ 28152344576 211061391360 239213735936
Conclusion
If you are an IT Administrator with limited development experience, the scripts in this post might look a bit intimidating at first, but please refer to my previous post to learn a bit about control structures and also how to save them as PS1 script files so you can re-use them. If you are a developer, I encourage you to investigate the many, many other WMI classes and start putting all that information to good use. Get started at http://msdn.microsoft.com/en-us/library/aa394084(v=VS.85).aspx
If you want to skip the hidden administrative shares ONLY (and not all of the hidden shares), I would suggest to change
($Share.Name[$Share.Name.Length-1] -ne "$")
to
($_.type -eq 0)
Type 0 (0x0) is Disk Drive.
Great idea! I have updated the scripts to use Type -eq 0...