Getting Folder Details from Remote Computers and NAS using PowerShell

This post is all about getting details about shared folders from remote computers using few techniques. We can get various information like last modified time, ACLs, etc. But it can get time consuming in a large production environment at the point when we have to calculate folder size.

Method 1: Measure-object

This can work for a small environment, but can take unreasonable long time on large work environment.

$CSVFile = "C:\logs.csv"
$machines = get-content "C:\servers.txt"
"Computer name,Foldername,Modified time,User/Group,Permissions" | out-file -FilePath $CSVFile -append

foreach ($machine in $machines) 
{
 $items = Get-ChildItem \\$machine\h$\USER , \\$machine\h$\USERS
 foreach ($item in $items)
  {
<#Check only folders and filter out files#>
   if ($item.Attributes -eq "Directory")            
    {         
<#-----Get folder name--------------------#>
     $FoldName = $item.Name          
     Write-Host $FoldName
<#----------Get folder size---------------#>  
$size = ($item | measure Length -sum).sum/1mb
<#------Get last modified timestamp-------#>

If ($size -eq "0.00 MB")
 {
  $ModTime = $fol.lastwritetime.ToString("dd/MM/yyy hh:mm")
 }
 else
  {
   $latest = Get-ChildItem -Path $fol.FullName -recurse -force | Sort-Object LastwriteTime -Descending | Select-Object -First 1
   $ModTime=$latest.lastwritetime
  }

<# We will collect information about ACL where Folder name 
then matches the name of user
Also we can employ any other condition for our purpose here #>
     $ACLs = get-acl $item.fullname | ForEach-Object { $_.Access  }
     Foreach ($ACL in $ACLs)
      {
       if ($ACL.IdentityReference -eq “Company\" + $FoldName)
        {
         $OutInfo = $machine+","+$FoldName+","+$ModTime+","+$ACL.IdentityReference+","+$ACL.FileSystemRights+","+$size
         Write-Host $OutInfo
         Add-Content -Value $OutInfo -Path $CSVFile
        }
      }
    }
  } 
}

Issue of last modified time

An important point to be noted in above script is that when we want to get the last modified time of a folder, we practically want -> ” What is the latest modifed item inside that folder as of now?”
So in this case, below code is more helpful.
$latest = Get-ChildItem -Path $fol.FullName -recurse -force | Sort-Object LastwriteTime -Descending | Select-Object -First 1
$ModTime=$latest.lastwritetime
As compared to this.
$ModTime = $fol.lastwritetime.ToString(“dd/MM/yyy hh:mm”)
But when the folder is empty, we really have no choice, but to take up the second option, which is why there is a correpsonding size check in the code above.

Method 2: File system object

It is observed that calculating folder size by using file system object deliver higher speed and more accurate folder sizes.

$fso = New-Object -comobject Scripting.FileSystemObject
$folder = $fso.GetFolder($path)
$size = [math]::round(($folder.size)/1mb , 2 , 1 )

Method 3: PSExec

There are certain things we need to ensure before proceeding with this method. Firstly, PSExec service must not be in disabled or marked for deletion state in target remote server. Secondly, the account launching the script should have appropriate administrative privileges. Thirdly, execution of scripts must not be disabled in remote computers via group policy.

Step 1> Proceeding with this method, first create a runme file with name, say runme.ps1 as follows:

$CSVFile = "C:\shared_folder\logs.csv"
"File Server,P Folder Name,P Folder Size,Last Modified time,`
User Name,NTFS Access" | out-file -FilePath $CSVFile -append
$machines = get-content "C:\shared_folder\servers.txt"
ForEach ($machine In $machines) 
{
 if (Test-Connection -ComputerName $machine -Count 2 -ErrorAction SilentlyContinue)   
  {
   Write-Host "$machine is up" -ForegroundColor Green
<#-----& is an alternative of start-process-------
accepteula switch will bypass dialog of accepting eula agreement #>     
& C:\Windows\System32\psexec.exe \\$machine -u Company\admin -p ### -h -accepteula1 cmd.exe /c "\\server1\shared_folder\Script.bat"  
  }
 else    
  {
   Write-Host "$machine is down" -ForegroundColor Red
   $info = $machine + "," + "is down"
   Add-content -value $info -Path $CSVFile
  }
}

Step 2> Above script will call out to following batch file “Script.bat” over network.

@Echo Off
Net Use T: /delete /yes
Net Use T: \\server1\shared_folder
CMD /C powershell.exe -ExecutionPolicy Bypass -Command T:\Get_ACL.ps1
Net Use T: /delete /yes
Exit

Step 3> Above script will map the folder with script as a network drive on remote computer and then call out to following Get_ACL.ps1 file which is actually responsible for gathering shared folder information. Note that now we won’t use network path of folders, instead we will write complete local path. As the script runs locally on the remote computer, this script will offset the delay caused due to network latency.

$CSVFile = "T:\logs.csv"
$machine = hostname
$Folders = Get-ChildItem h:\USER , h:\USERS


After this we have our usual script where we can employ either method 1 or method 2.

This method has the highest speed among others mentioned in this post.

Details of folders stored in NAS

For fetching details of folders stored in NAS, we might be face to face with following challenges.

  • Since NAS is just a storage box, Powershell methods like PSEXEC and remote commands like ENTER-PSSESSION will fail.
  • Also NAS is accessed via UNC path and mostly that would require specific credentials, So we may need to devise a way to pass on these credentials in script.

So in a nutshell, we will just have a UNC path at our disposal, to access the NAS folders.

$CSVFile = "C:\NAS_ACL_Size.csv"

"File Server,P Folder Name,P Folder Size,Last Modified time,User Name,NTFS Access" |out-file -FilePath $CSVFile -append

$Folders = Get-Content "C:\NAS.txt"

#Match UNC path to corresponding $machine
foreach ($fol in $Folders){

If ($fol -like "*10.245.267.03*") { $machine = "Hawaii NAS"}

If ($fol -like "*10.211.237.99*") { $machine = "Kongo NAS" }

If ($fol -like "*10.202.257.99*") { $machine = "Serbia NAS"}

#Refine results to fetch just folders one level inside the UNC path

$fols = Get-ChildItem $fol -EA SilentlyContinue|Where-Object {$_.PSIsContainer -eq $true}
Foreach ($f in $fols){
<#-----Get folder name--------------------#>
$FoldName = $f.Name
$ACLs = get-acl $f.FullName | ForEach-Object { $_.Access  }

Foreach ($ACL in $ACLs) {

If ($ACL.IdentityReference -eq "mydomain\" + $FoldName) {

<#----------Get folder size---------------#> 
$subFolderItems = Get-ChildItem $f.FullName -recurse -force -EA SilentlyContinue | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum -EA SilentlyContinue | Select-Object Sum
$size = "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"

<#------Get last modified timestamp-------#>
If ($size -eq "0.00 MB"){                       
$ModTime = $f.lastwritetime.ToString("dd/MM/yyy hh:mm")                      
}                    
else{                        
$latest = Get-ChildItem -Path $f.FullName -recurse -force | Sort-Object LastwriteTime -Descending | Select-Object -First 1                        
$ModTime=$latest.lastwritetime                      
}

$OutInfo = $machine + "," + $FoldName + "," + $size +  "," + $ModTime + "," + $ACL.IdentityReference + "," + $ACL.FileSystemRights 
Write-Host $OutInfo 
Add-Content -Value $OutInfo -Path $CSVFile 
         } 
      }
   }
}

The script will require an input notepad file with UNC paths of NAS drives, as follows.

“\\10.245.267.03\\data\home drives”
“\\10.211.237.99\homes”
“\\10.202.257.99\data”

Match UNC path to corresponding $machine

Now in order to tell PowerShell that a certain UNC is of certain region NAS, we have employed a simple if statement, as follows.

If ($fol -like “*10.245.267.03*”) { $machine = “Hawaii NAS”}

Since we cannot use $machine = localhost, so now we have a way of assigning a proper value to variable $machine.

Refine results to fetch just folders

Also note that there are two ways to refine and fetch just the folders from results of get-childitem. I have used them seperately in two script examples in this post.

$fols = Get-ChildItem $fol |Where-Object {$_.PSIsContainer -eq $true}

And Second is,

$items = Get-ChildItem \\$machine\h$\USER , \\$machine\h$\USERS

foreach ($item in $items) {  

if ($item.Attributes -eq “Directory”)    {    

Passing the credentials in script

Now the challenge remains is passing the credentials in the script. A solution for this situation has been provided in this blog POWERSHELL + UNC PATH + CREDENTIALS

A solution could be dividing the script into blocks for different NAS drives, in case, each has different set of credentials. The block could look like below.

$uncFullPath = "\\10.245.267.03\data\home drives"
$username = "admin"
$password = "@dmin"

net use \\10.245.267.03\ $password /USER:$username

try {
#The complete code could come here
#we can directly specify $machine = "Hawaii NAS", so the code to get $machine can be removed.
}

catch [System.Exception] { 
WriteToLog -msg "encountered some error... $_.Exception.Message" -type Error 
}

finally {
net use \\10.245.267.03\ /delete 
}

 

Advertisements