Link to home
Start Free TrialLog in
Avatar of gerhardub
gerhardub

asked on

Using PowerShell to Total My Documents for ALL Domain Usres

Ok,

I'd like to use powershell to scan ALL of the workstations in my domain and total up the amount of space both the My Documents and Desktop folders user up.

I can provide a scipt a list of machines to check, but then I'd need to have it figure out all of the users on each machine and provide a total (probably in gigabytes).

This is what I have so far:

$path = [environment]::GetFolderPath([environment+SpecialFolder]::MyDocuments)

 $totalSize = Get-ChildItem -path $path -recurse -errorAction "SilentlyContinue" |
 Measure-Object -property length -sum

$path2 = [environment]::GetFolderPath([environment+SpecialFolder]::Desktop)

 $totalSize2 = Get-ChildItem -path $path2 -recurse -errorAction "SilentlyContinue" |
 Measure-Object -property length -sum

 IF(($totalSize.Sum+$totalSIze2.Sum) -ge 1GB)
   {
      "{0:n2}" -f  (($totalSize.Sum+$totalSIze2.Sum) / 1GB) + " GigaBytes"
   }
 ELSEIF($totalSize2.sum -ge 1MB)
    {
      "{0:n2}" -f  (($totalSize.Sum+$totalSIze2.Sum) / 1MB) + " MegaBytes"
    }
 ELSE
    {
      "{0:n2}" -f  (($totalSize.Sum+$totalSIze2.Sum) / 1KB) + " KiloBytes"

Anyone have a script that does what I want?  ...or can you suggest modificaitons to read a file, scan a remote computer, and perform the above for all users on that computer?
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image


Bit of a multi-stage one this, while I figure out how to reliably get from one item to the next :)

Profiles on a machine, we can discover those with:

Get-WMIObject Win32_UserProfile -Filter "Special='$False'"

That gets the profile but not the folder paths, they may or may not be under the "LocalPath" property returned by that query.

/me goes away to think

Chris
Hmm the next step is actually quite hard.

If everything is straight-forward we can assume that "Desktop" and "My Documents" appear directly beneath the path returned above.

The trouble comes when that isn't the case, then we're in a bit of a mess. This value:

[environment]::GetFolderPath([environment+SpecialFolder]::MyDocuments)

Ultimately you get that from HKEY_CURRENT_USER. When we're asking for all users we're no longer looking at the current user, HKEY_USERS is more appropriate. The trouble with that is you'll only see registry hives which have been loaded; users that have logged on since the last reboot.

Having PowerShell load the hive is extremely unlikely to happen at this stage (as far as I've seen), too many issues with permissions before we touch on trying to wrap up an API so it can be used in a .NET language (like PowerShell).

In short... are you quite happy to play guessing games with the paths for My Documents and Desktop given that we have the path to the profile?

Chris
Avatar of gerhardub
gerhardub

ASKER

Go ideas.

Let's make the assumption that all of the computers that we are going to check are Window XP.

(So that means c:\Documents and Settings\<User Profile>)

So yeah, the guessing game part is fine!
(I ment to say good ideas!)

FYI:

I see this as something like:

1) Go to the computers in the domain, probably by providing a computer.txt document with all of the computers you want to scan.

2) Find the user profiles listed under C:\Document and Settings

3) Look at the total size of My Documents and Desktop

4) Write the agreegate total for all users on all workstations to the screen or in a file.
Okie dokie. Lets give this a whirl then.

Only tested in PowerShell 2, gave up on 1 quite a while ago.

I've tried to account for the different in "My Documents" between XP /2003 and Vista / 7 / 2008.

Output is to the screen at the moment, but we can redirect to lots of things, using any of these:

Out-GridView
Export-CSV
Out-File
ConvertTo-Html
etc

Chris
# Read the import file
Get-Content SomeFile.txt | %{
  # Store computer name in a named variable for access later
  $ComputerName = $_
  # Get the operating system version
  $Version = (Get-WMIObject Win32_OperatingSystem -Computer $ComputerName).Version

  # Get all configured user profiles (ignore special accounts)
  Get-WMIObject Win32_UserProfile -Computer $ComputerName -Filter "Special='$False'" | %{

    # Convert the local path to a UNC path
    $UNCPath = "\\$ComputerName\$($_.LocalPath -Replace ':\\', '$\')"

    # Initialise the size values
    $DesktopSize = 0; $MyDocumentsSize = 0
    # Attempt to get the desktop size
    If (Test-Path "$UNCPath\Desktop")
    {
      $DesktopSize = (Get-ChildItem "$UNCPath\Desktop\" -Recurse | Measure-Object Length -Sum).Sum
    }

    # Check version then get the My Documents size
    If ($Version -Like "6*")
    {
      If (Test-Path "$UNCPath\Documents")
      {
        $MyDocumentsSize = (Get-ChildItem "$UNCPath\Documents\" -Recurse | Measure-Object Length -Sum).Sum
      }
    }
    Else
    {
      If (Test-Path "$UNCPath\My Documents")
      {
        $MyDocumentsSize = (Get-ChildItem "$UNCPath\My Documents\" -Recurse | Measure-Object Length -Sum).Sum
      }
    }

    # Leave this object in the output pipeline
    $_ | Select-Object `
      @{n='ComputerName';e={ $ComputerName }},
      LocalPath, SID,
      @{n='AccountName';e={ 
        $Account = [WMI]"Win32_SID.SID='$($_.SID)'"
        If ($Account.AccountName -ne $Null) {
          "$($Account.ReferencedDomainName)\$($Account.AccountName)"
        }
      }},
      @{n='LastUseTime';e={ [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) }},
      @{n='TotalSize';e={ $DesktopSize + $MyDocumentsSize }},
      @{n='DesktopSize';e={ $DesktopSize }},
      @{n='MyDocumentsSize';e={ $MyDocumentsSize }}
  }
}

Open in new window

Sweeeet!  

I just played with this, and the only part I'm having a little issue with is the 'Leave this object in the output pipepline" section.

Seems to be giving me an editor error.  Humph.  (Back to you shortly.)

GB
It may be an inconsistency between PS versions.

If it's trying to parse it for PS1 you'll need a ` at the end of each of the property lines, otherwise it won't drop down and continue it as a single command. An example of that below.

Bit of a complex section though, I'd hate to say it was entirely down to that.

Chris
# Leave this object in the output pipeline
    $_ | Select-Object `
      @{n='ComputerName';e={ $ComputerName }}, `
      LocalPath, SID, `
      @{n='AccountName';e={ 
        $Account = [WMI]"Win32_SID.SID='$($_.SID)'"
        If ($Account.AccountName -ne $Null) {
          "$($Account.ReferencedDomainName)\$($Account.AccountName)"
        }
      }}, `
      @{n='LastUseTime';e={ [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) }}, `
      @{n='TotalSize';e={ $DesktopSize + $MyDocumentsSize }}, `
      @{n='DesktopSize';e={ $DesktopSize }}, `
      @{n='MyDocumentsSize';e={ $MyDocumentsSize }}

Open in new window

Friggen back tick!  -grin-  That problem solved, it's a neat solution.

I'm getting a lot of Invalid Class errors from WMI, so I'm trying to track that down:

Get-WmiObject : Invalid class
At C:\Users\<USERID>\Documents\Scripts\CheckMyDocsDesktopFromComputerList.ps1:21 char:16
+   Get-WMIObject <<<<  Win32_UserProfile -Computer $ComputerName -Filter "Special='$False'" | %{
    + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

I wonder what makes that an Invalid Operation?

Ah... damn... minimum supported client for Win32_UserProfile is Vista:

http://msdn.microsoft.com/en-us/library/ee886409%28VS.85%29.aspx

Let me see if I can find an alternative that'll run for Windows XP.

Chris

We can pull equivalent data out of HKEY_LOCAL_MACHINE ... ProfileList, I just need to prod the code a bit again.

Chris
Crap!

That's explains SO much.  So I was able to get it to work, by accident, on a bunch of systems.

(It turned out I already had a file named 'computer.txt,' which was populated with servers... so 2003 and some 2008.  Any it failed on the non-2008 servers... -smile-)

Thanks for all of your help, I really appricate this, as, in general, it's been kicking me in the butt.
ASKER CERTIFIED SOLUTION
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Dude... this is really great!

Now I just have to figure out how to solve the authentication issue so that I don't have to logon 400 times!

Still, it's a great start!

Lemme know if you come up with an authentication solution.  
Excellent solution.

Anyone have any thoughts on how to not have to authenticate for each machine?

GB
The simplest solution would be Run As :)

But if that's no good we can stick credentials in a variable and pass those for the WMI connections.

Putting them into a variable is as simple as adding this to the beginning of the script:

$Credential = Get-Credential

Each of the Get-WMIObject commands, and my Get-UserProfile function, would need that added with the credential parameter:

Get-WMIObject .... -Credential $Credential
Get-UserProfile .... -Credential $Credential

Now all we have to deal with is authenticating against the file system so we can pull the file sizes. Naturally that's harder to do and I'll have to have a think about that one. Worst case scenario will have us temporarily mapping a drive to it using "net use" and taking it from there.

Chris
Chris,

Lemme see if I can ask you another question:

If you where going to output this to a CSV file, maybe one line per workstation checked, how would you do it?

GB

If I were to dump it in it's current state to CSV it's as simple as adding "| Export-CSV" after the very last curly brace. The downside to that is you'll get multiple lines per machine.

Moving everything onto a single line is pretty hard, it's the kind of thing that lends itself better to a hierarchical structure like XML more than CSV. That doesn't mean it's impossible of course, it's just a case of defining exactly what you want to see and making that possible. What format is the most useful to you?

If it were mine I would leave the format as it is, but my queries against the data are quite likely to be within PowerShell, or perhaps SQL if I chuck it into a database. If I wanted to display it I would perhaps be tempted to use ConvertTo-Html, eventually making something similar to the ACL report generated by the script that came out of this thread:

http:Q_25066855.html

Chris
Hey Chris-dent

I'm a novice with powershell with that said I tried to run your script and it complained about your SomeFile.txt missing from S:  so after I created the file the script ran fine but that file is blank. I also had it export to a csv file on my desktop but its blank to. Any help would be much appreciated.

Thanks