Measure-object use in advanced function; no totals

This topic contains 7 replies, has 3 voices, and was last updated by Profile photo of Peter Marsh Peter Marsh 5 months, 3 weeks ago.

  • Author
    Posts
  • #41962
    Profile photo of Peter Marsh
    Peter Marsh
    Participant

    (Ref# 40398)
    I have verified that measure-object works on a collection of objects obtained from get-childitem. Here is the code"

     $var1=Get-ChildItem -path C:\Users\Peter\Documents\TestArchive -Recurse
    $var1 | Measure-Object -InputObject {$_.length} -sum | Select-Object @{name='Total File Size in GB';expression={$_.sum/1e9}}, @{name='File Count';expression={$_.count}}
    

    Here is the output:

    Total File Size in GB File Count
    ——————— ———-
    0.039386879 37

    Here is the function code I am trying to build:

        Process{
        $total=Get-ChildItem -Path C:\Users\Peter\Documents\TestArchive -Recurse 
            foreach($file in $total){
            if((Get-ItemProperty -Path $file.FullName).Attributes -band [io.fileattributes]::archive){
            Measure-Object -inputobject ($file).length -sum | Write-Output
    

    The if statement returns true for files that have the archive bit set; then the measure-object cmdlet works on each file one at a time, instead of the entire collection of files as in the example above. So I get this output:

    Count    : 1
    Average  : 
    Sum      : 1100072
    Maximum  : 
    Minimum  : 
    Property : 
    
    Count    : 1
    Average  : 
    Sum      : 1148510
    Maximum  : 
    Minimum  : 
    Property : 
    

    etc.. I've tried using hash tables, but no luck. Any ideas out there?
    Peter

  • #41978
    Profile photo of Jack Neff
    Jack Neff
    Participant

    So it's pretty obvious when each measure object has a count of '1' that the foreach loop is breaking the files down and measuring them individually. If you want to do it that way, you have to accumulate their length values into a variable like this:

    $Files = Get-ChildItem -Path 'C:\Users\Peter\Documents\TestArchive' -Recurse
    foreach ($File in $Files){
        if((Get-ItemProperty -Path $file.FullName).Attributes -band [io.fileattributes]::archive){
            $TotalSize = $TotalSize + $File.Length
        }
    }
    $SizeByGB = $TotalSize / 1GB
    "I have {0} files with a total size of {1:N2}" -f $Files.Count, $SizeByGB
    

    Still this line worries me. Casting, enumeration, binary and, OH MY???

    if((Get-ItemProperty -Path $file.FullName).Attributes -band [io.fileattributes]::archive)

    Don't get too ahead of yourself, take time to learn the basics it'll help you deconstruct lines like this and sometimes you may find there's an easier way to accomplish the same task.

    $Files = Get-ChildItem 'C:\Users\Peter\Documents\TestArchive' -Attributes Archive -Recurse
    $TotalFiles = $Files | Measure-Object -Property Length -Sum
    $SizeByGB = $TotalFiles.Sum / 1GB
    "I have {0} files with a total size of {1:N2}" -f $TotalFiles.Count, $SizeByGB
    
  • #41982
    Profile photo of Jack Neff
    Jack Neff
    Participant

    Spotted a flaw in my first code block after posting but I'll let you find it. ; )

  • #41989
    Profile photo of Peter Marsh
    Peter Marsh
    Participant

    Jack: Was the flaw not initializing the variable $Totalsize? I did so in my begin block, along with another variable $Totalnumber. The line you mention was lifted directly from Ed Wilson scripting blog. I see how it works, and it seems very succinct: for each file found, the get-property cmdlet examines it to see if there is an archive bit set. all other attributes are ignored. If the file is archive, then the boolean resolves to 1 = true, and the next two lines of code are executed. This was so simple. I'm just not thinking like a programmer...been many years since Fortran Buff 40. Here is the new function, which works fine:

    function Get-ArchiveSize
    {
        [CmdletBinding()]
        Param
        (
            # Path to the head archive directory is required
            [Parameter(Mandatory=$true,
                        ValueFromPipeline=$true,
                       ValueFromPipelineByPropertyName=$true,
                       Position=0)]
                      [string] $Path
        )
    
        Begin
        {$totalsize=0
         $totalnumber=0
        }
        Process{
        $total=Get-ChildItem -Path $Path -Recurse 
            foreach($file in $total){
            if((Get-ItemProperty -Path $file.FullName).Attributes -band [io.fileattributes]::archive){
            $totalsize=$totalsize + $file.length
            $totalnumber=$totalnumber + 1}
    }
           $hash=[ordered]@{'Total Size of Files in GB'=$totalsize/1e6;
                     'Total Number of Files'=$totalnumber}
                       $Obj=New-Object -TypeName PSObject -Property $hash
                               Write-Output $Obj}
     }
    

    BTW, I also was using lines #1 and 2 in your example, but I believe they are incorrect code. Child-item has two parameter sets, and -path, -recurse are in the first set, and -attributes is only in the second set. I was getting inconsistent results and finally remembered something from the first Powershell class where Jason was explaining parameter syntax. Also, the -property parameter for measure-object does not accept pipeline input: you must use -inputobject. So instead, I began using this code:

     
    $var1=Get-ChildItem -path C:\Users\Peter\Documents\TestArchive -Recurse
    $var1 | Measure-Object -inputobject {$_.length} -Sum
    

    However, I was now back to selecting ALL files. I also tried using where-object -property attributes, but this was also a non-pipeline parameter. Again, it seemed to work, but files that were both archive and readonly were not picked up. So Ed Wilson seemed to have the only solution for identifying a file with any attribute (maybe).
    I haven't seen the -f feature in your last line. What is that?

  • #42009
    Profile photo of Max Kozlov
    Max Kozlov
    Participant

    Filtering by -Attributes and by Where-Object give equal results

    PS D:\> Get-ChildItem -Path d:\! -Recurse  | Where-Object { $_.Attributes -band [io.fileattributes]::readonly } | Measure-Object -Sum -Property Length
    
    Count    : 2
    Average  :
    Sum      : 7059789312
    Maximum  :
    Minimum  :
    Property : Length
    
    PS D:\> Get-ChildItem -Path d:\! -Recurse -Attributes Readonly | Measure-Object -Sum -Property Length
    
    
    Count    : 2
    Average  :
    Sum      : 7059789312
    Maximum  :
    Minimum  :
    Property : Length
    

    -f – is formatting operator

    Get-Help About_Operators

    http://ss64.com/ps/syntax-f-operator.html

    #btw you can free use any combinations :)
    # find readonly inside 'archive'
    PS D:\> Get-ChildItem -Path d:\! -Recurse -Attributes Archive | Where-Object { ($_.Attributes -band 'readonly')  } | Measure-Object -Sum -Property Length
    
    
    Count    : 3
    Average  :
    Sum      : 7059790161
    Maximum  :
    Minimum  :
    Property : Length
    
    # the same thing
    PS D:\> Get-ChildItem -Path d:\! -Recurse | Where-Object { ($_.Attributes -band 'readonly') -and ($_.Attributes -band 'Archive')  } | Measure-Object -Sum -Property Length
    
    
    Count    : 3
    Average  :
    Sum      : 7059790161
    Maximum  :
    Minimum  :
    Property : Length
    
    # readonly with no archive bit
    PS D:\> Get-ChildItem -Path d:\! -Recurse | Where-Object { ($_.Attributes -band 'readonly') -and (-not ($_.Attributes -band 'Archive'))  } | Measure-Object -Sum -Property Length
    
    
    Count    : 1
    Average  :
    Sum      : 849
    Maximum  :
    Minimum  :
    Property : Length
    
    • This reply was modified 6 months ago by Profile photo of Max Kozlov Max Kozlov.
    • This reply was modified 6 months ago by Profile photo of Max Kozlov Max Kozlov.
  • #42021
    Profile photo of Jack Neff
    Jack Neff
    Participant

    Jack: Was the flaw not initializing the variable $Totalsize?

    Nope, PS is pretty good about not having the declare simple variable types like integers but it is good practice to declare them anyway b/c you could unintentionally keep adding to that variable each time you run the script giving confusing results. My flaw was $Files.Count represented the total number of files, not just the ones with the archive attribute on.

    BTW, I also was using lines #1 and 2 in your example, but I believe they are incorrect code. Child-item has two parameter sets, and -path, -recurse are in the first set, and -attributes is only in the second set.

    Are you sure? Max did a good job testing this out.

    Also, the -property parameter for measure-object does not accept pipeline input: you must use -inputobject.

    You're right in that the -Property param does not take pipeline input but if you look again I didn't pipe that value to Measure-Object. Furthermore, I think you're mixing up the -Property and -InputObject params. Looks like you're using -InputObject to specify what property you want to be measured but that is in fact the purpose of the -Property param. -InputObject is used to measure a single object and was designed to be used "instead of piping".

    I haven't seen the -f feature in your last line. What is that?

    I learned that little trick from someone on this forum a few years ago. I'd love link to his posts to give him credit but I can't seem to search my old posts and the site is crawling for me this morning. But yea, it's handy to insert variables into an output string without making it really messy.

  • #42046
    Profile photo of Jack Neff
    Jack Neff
    Participant

    I learned that little trick from someone on this forum a few years ago. I'd love link to his posts to give him credit but I can't seem to search my old posts and the site is crawling for me this morning.

    And not long after I post this, he posts on another thread, haha... life is cool. Rob Simmers

  • #43780
    Profile photo of Peter Marsh
    Peter Marsh
    Participant

    Max, thanks for showing me the correct syntax for combined boolean expressions used with where-object. I must have tried different combinations for an hour or so. I have saved these examples in my script folder.

    Jack, I was certainly confused about how cmdlets like measure-object and select-object work in the pipeline. I went back and read chapters on the pipeline in "Learn Windows Powershell..." and I see what is going on. The -property parameter does not accept pipeline input, but it doesn't have to; the connection is automatic since this cmdlet accepts ALL types of input. I saw an explanation of the difference in using input parameter where the entire collection of objects gets passed through as one array. Thanks again

    Now onto finishing "Learn Powershell Toolmaking in a Month of Lunches" so I can build a module...
    Peter

You must be logged in to reply to this topic.