Collect disk space details from remote computers

In this post we will discuss some PowerShell scripts to get the details like, used space, free space, total size, etc. of all the drives on remote computers. The list of remote computers could be fed as a text file to the script. Lets discuss each scripts one by one.

Using Win32_LogicalDisk

$CSVFile = "C:\Disk space report.csv"
"Computer Name;Volume Name;Drive Name;Size(GB);Free(GB);Used(GB);Used%(GB)" | out-file -FilePath $CSVFile -append

#domain admin's credentials
$cre = get-credential
$machines = get-content "C:\serverset.txt"
foreach ($machine in $machines) 
$disk=Get-WmiObject Win32_LogicalDisk -filter "DriveType=3" -computer $machine -Credential $cre| Select SystemName,DeviceID,VolumeName,Size,freespace
foreach ($d in $disk)
#to set language pack for current thread as en-US
[threading.thread]::CurrentThread.CurrentCulture = 'en-US'
$DeviceID=($d.DeviceID).Replace(":", "")

#Round(($d.size/1gb), 2)) will round the value to two decimal places
$size=('{0:N2}' -f [Math]::Round(($d.size/1gb), 2))

$freespace=('{0:N2}' -f [Math]::Round(($d.freespace/1gb), 2))
#we have to define the values stored in $d.size and $d.freespace as integer
$usespace=('{0:N2}' -f ([Math]::Round($d.size/1GB,2 -as [int])) - ([Math]::Round($d.freespace/1GB,2 -as [int])))

$usespacepc=('{0:N2}' -f [Math]::Round((($usespace/$size)*100), 2))

$exporttocsv =$machine+";"+$DeviceID+";"+$d.VolumeName+";"+$size+";"+$freespace+";"+$usespace+";"+$usespacepc
$exporttocsv | Out-File -Append $CSVFile

Now where on one hand we can get free space quite easily, we need to perform calculations to get used space.

Using Get-PSDrive

Now we will try to do the same thing, but this time we won’t do any calculation. As a result this can lead to higher speed and accuracy. We’ll try to make use of get-psdrive command to get used space and free space directly. However we still need to use get-wmiobject command to get total size of drive and volumename.

$CSVFile = "C:\Disk space report.csv"
"Computer Name;Drive Name;Volume Name;Size(GB);Free(GB);Used(GB);Used%(GB)" | out-file -FilePath $CSVFile -append

$cre = get-credential
$machines = get-content "C:\serverset.txt"
foreach ($machine in $machines) {
#---To get Volume names and Total size, we will-----
#---use Win32_LogicalDisk, and to get Used Space----
#---and free space, we will use Get-PSDrive---------
$disk=Get-WmiObject Win32_LogicalDisk -filter "DriveType=3" -computer $machine -Credential $cre | Select Size,DeviceID,VolumeName

$disk1=Invoke-Command -ComputerName $machine {Get-PSDrive} -Credential $cre | Select-Object PSComputerName,Name,Used,Free,Provider|
where-object Provider -match "FileSystem"

foreach ($d in $disk)
[threading.thread]::CurrentThread.CurrentCulture = 'en-US'
$deviceid=($d.DeviceID).Replace(":", "")

foreach ($d1 in $disk1)

if ($d1.Name -match $deviceid){
$VolumeName = $d.VolumeName
[threading.thread]::CurrentThread.CurrentCulture = 'en-US'
$size=('{0:N2}' -f [Math]::Round(($d.size/1gb), 2))

$freespace=('{0:N2}' -f [Math]::Round(($, 2))

$usespace=('{0:N2}' -f [Math]::Round(($d1.used/1gb), 2))

$usespacepc=('{0:N2}' -f [Math]::Round((($usespace/$size)*100), 2))

$exporttocsv =$d1.PSComputerName+";"+$deviceid+";"+$VolumeName+";"+$size+";"+$freespace+";"+$usespace+";"+$usespacepc
$exporttocsv| Out-File -Append $CSVFile
Break }

Using Win32_Volume

Now we will see how we can make use of Win32_Volume to get the desired output.

$CSVFile = "C:\Disk space report.csv"
"Computer name,Drive name,Size(GB),Free(GB),Used(GB),% Used" | out-file -FilePath $CSVFile -append
$machines = get-content "C:\Users\umurarka\Desktop\servers.txt"
$cre = Get-Credential
foreach ($machine in $machines) 

$disk = Get-WmiObject - -ComputerName $machine -Class Win32_Volume -ErrorAction SilentlyContinue
foreach ($d in $disk)

$size=('{0:N2}' -f [Math]::Round(($d.Capacity/1gb), 2))

$freespace=('{0:N2}' -f [Math]::Round(($d.freespace/1gb), 2))

$usespace=('{0:N2}' -f ([Math]::Round($d.Capacity/1GB,2 )) - ([Math]::Round($d.freespace/1GB,2)))

$usespacepc=('{0:N2}' -f [Math]::Round((($usespace/$size)*100), 2))

$exporttocsv ="acrdefrps01"+" "+$d.DriveLetter+" "+$size+" "+$freespace+" "+$usespace+" "+$usespacepc
$exporttocsv| Out-File -Append $CSVFile

A Fast Way of getting disk space report through UNC paths

Using above methods sometimes come with problems like access denied. To overcome these challenges, we can use UNC paths to solve our problem. Also it has many times higher speed than any of the above method.

#---function for geeting disk space from UNC--------
[threading.thread]::CurrentThread.CurrentCulture = 'en-US'
function getDiskSpaceInfoUNC($p_UNCpath, $p_unit = 1GB, $p_format = '{0:N1}')
    # unit, one of --> 1kb, 1mb, 1gb, 1tb, 1pb
    $l_typeDefinition = @' 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
        [return: MarshalAs(UnmanagedType.Bool)] 
        public static extern bool GetDiskFreeSpaceEx(string lpDirectoryName, 
            out ulong lpFreeBytesAvailable, 
            out ulong lpTotalNumberOfBytes, 
            out ulong lpTotalNumberOfFreeBytes); 
    $l_type = Add-Type -MemberDefinition $l_typeDefinition -Name Win32Utils -Namespace GetDiskFreeSpaceEx -PassThru

    $freeBytesAvailable     = New-Object System.UInt64 # differs from totalNumberOfFreeBytes when per-user disk quotas are in place
    $totalNumberOfBytes     = New-Object System.UInt64
    $totalNumberOfFreeBytes = New-Object System.UInt64

    $l_result = $l_type::GetDiskFreeSpaceEx($p_UNCpath,([ref]$freeBytesAvailable),([ref]$totalNumberOfBytes),([ref]$totalNumberOfFreeBytes)) 

    $totalBytes     = if($l_result) { $totalNumberOfBytes    /$p_unit } else { '' }
    $totalFreeBytes = if($l_result) { $totalNumberOfFreeBytes/$p_unit } else { '' }

    New-Object PSObject -Property @{
        Path      = $p_UNCpath
        Total     =  [math]::round($totalBytes , 2 , 1)
        Used      =  [math]::round($totalBytes -  $totalFreeBytes , 2 , 1)
        Used_percent      = [math]::round((($totalBytes -  $totalFreeBytes)/$totalBytes)*100 , 2 , 1)
        Free      = [math]::round($totalFreeBytes , 2 , 1)

$CSVFile = "C:\Disk space report.csv"
$machines = get-content "C:\servers.txt"
foreach ($machine in $machines) 
#---With the help of Win32_Share, we will filter----
#---and refine our search results to include only---
#---the drive letters, e.g. C$----------------------
$ds = get-WmiObject -class Win32_Share -computer $machine -ErrorAction SilentlyContinue|
? {($_.description -NotLike "*printer*") -and ($ -notlike "SYSVOL") -and ($ -like "?$")} |
 select Name

 foreach ($d in $ds) {
 $p = join-path \\$machine\ $

[threading.thread]::CurrentThread.CurrentCulture = 'en-US'
getDiskSpaceInfoUNC $p |ft -AutoSize
getDiskSpaceInfoUNC $p |ft -AutoSize -HideTableHeaders| Out-File -Append $CSVFile


Editting folder permissions with PowerShell

In this post, we will discuss about modifying folder NTFS permissions without GUI on a Windows platform. Modifying NTFS permissions for a small environment by manual GUI means is understandable, but it can get really hectic and time consuming for a large environment. Now lets have a look at the following Powershell Script that is a step in the direction of automating this task.

$CSVFile = "C:\log.csv"
$machines = get-content "C:\servers.txt"
foreach ($machine in $machines)
<# get all network path names of shared folders from another text file, 
then process through each path recursively
sample path file:
Instead of inputting text file, we can also use:
$paths = gi "H:\Users\*","H:\Departments\*" #>
 $paths = (get-content "C:\paths.txt").replace('$machine', $machine)
 foreach ($path in $paths)
<# -----part-1 (disable inheriance)-----
To change access of an object on folder which has been inherited
from parent folder, we need to disable the inheritance first.
So, traverse one level down in each path, then check there only for 
folders, then disable inheritance at that level. #>
   get-childitem -Path $path\* | ? { $_.PsIsContainer } | % {
<# We could have also used $acl = Get-Item $_ | get-acl
But this would not work in cases where the account who is launching 
the script is not an owner of the folder whose permissions are being 
changed. So we need to tell powershell to only look for 'Access' by 
using a piped code structure. #>    
   $acl = (Get-Item $_).GetAccessControl('Access')
<# first arguement ($true) will disable inheritance and 
second arguement ($false) will remove all ACEs inherited  
from parent folder. #>
   $acl.SetAccessRuleProtection($true, $false)
   Set-Acl -path $path -aclObject $acl | Out-Null
<# ----part-2 changing premissions of an account---
To avoid the problem of ownership, here also we first
try to tell powershell that we are only interested in 
accesses of each acl and not anything extra, by using
a loop structure. #>
   $acl2 = Get-Acl -path $path
   $accesses = $acl2.Access
# get ace from each acl recursively
   foreach ($access in $accesses)
     $ids = $access.IdentityReference.Value
     foreach ($id in $ids)
       if ($id -eq "$machine\User")
<# when conditions true, remove all permissions for 
the mentioned account only #>
         $acl2.RemoveAccessRule($access) | Out-Null
<# provide Read and Execute permission at parent or root folders level 
to the same account #>
     Set-Acl -path $path -aclObject $acl2 | Out-Null
     $permission = $name,'ReadandExecute','ContainerInherit,ObjectInherit','None','Allow'
     $aclrule = New-Object -ArgumentList $permission
     Set-Acl -path $path -aclObject $acl2 | Out-Null
     $OutInfo = $machine+","+$path+","+$aclrule.IdentityReference+","+$aclrule.FileSystemRights
     Write-Host $OutInfo             
     Add-Content -Value $OutInfo -Path $CSVFile                                

Here Inheritance flag is set for both container and object whereas Propogation flag is none.

Following is a table which will help in setting propogation and inheritance flags.

    ║             ║ folder only ║ folder, sub-folders and files ║ folder and sub-folders ║ folder and files ║ sub-folders and files ║ sub-folders ║    files    ║
    ║ Propagation ║ none        ║ none                          ║ none                   ║ none             ║ InheritOnly           ║ InheritOnly ║ InheritOnly ║
    ║ Inheritance ║ none        ║ Container|Object              ║ Container              ║ Object           ║ Container|Object      ║ Container   ║ Object      ║

We can speed up things by employing psexec utility for above PowerShell script as discussed in my previous posts. For some reason get-acl and set-acl fail to function for folders on which the current user is not an owner of. Set-acl tries to change the owner of folder which is certainly not the intention in most cases.

For further reading, refer to this technet link: Windows PowerShell Tip of the Week where  [System.Security.AccessControl.FileSystemRights] (a .NET Framework file system enumeration) and security descriptors are discussed in greater detail.

There is another approach to above scenario, which makes use of a powershell module “NTFSsecurity” File System Security PowerShell Module 4.2.3 by Raimund Andree. We can achieve the desired effect by proceeding as follows.

<#First we need to import the module "NTFSSecurity"
If we want the module to be used by all users on the system, 
then it should be #placed under the path: 
C:\Program Files(x86)\WindowsPowerShell\Modules\NTFSSecurity #>
Import-Module C:\Program Files (x86)\WindowsPowerShell\Modules\NTFSSecurity
<#This will list out all the commands which are 
available in this module#>
Get-Command -Module ntfssecurity
<#This will disable inheritance at only one level below the root 
"test" folder. Remove the \* from path, if inheritance is to 
be disabled at all levels inside.#>
get-item "\\server1\test\*" | Disable-NTFSAccessInheritance
<#Now we first get the access of the account whose access we want to 
change, then we remove its access from the folder.
Now we add the desired access of the same account to the folder.This 
is particularly useful when we want to remove the special permissions 
provided to any account, which may present a security risk.#>
Get-Item -Path "\\server1\test" |Get-NTFSAccess -Account Users | Remove-NTFSAccess
Add-NTFSAccess -Path "\\server1\test" -Account Users -AccessRights ReadAndExecute -AppliesTo ThisFolderAndSubfoldersOneLevel

Before proceeding with the solutions presented in this post, do take a full backup of ACls of the folder in question using icacls command. This technet blog How to Back Up and Restore NTFS and Share Permissions  provides all the details required for performing a quick backup and restore of ACLs.

By wracking mind with these and many other solutions sometimes we feel like that sticking to basics does the job best. A simple command line approach is both faster and accurate in a mass folder structure. We can use icacls to do the same stuff what we discussed so far by putting far lesser effort. For this purpose I a sharing this link iCACLS.exe (2003 sp2, Vista+), which has got all the applications of this command covered.