PowerShell

Getting file metadata with PowerShell similar to what Windows Explorer provides

I'm working on a new feature for one of my modules that requires me to know what kind of files I am working with. It's quite easy in PowerShell, and without a lot of code, you can reasonably quickly get necessary information about data stored on your desktop or anywhere else for that matter.

Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force

Or if you want to see all available properties for an object you can display it this way.

Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Select-Object -First 1 |  Format-List *

This, of course, is not all available data. If you right-click on any file and switch tab to Details, you will see a lot of special info is available. What's unique about it – is that each file type will have different kinds of information.

As you can see above I've quickly checked Excel file, MSI file, an EXE file, all having similar but not the same information.

Getting file metadata with PowerShell

Now that you know what I'm after, let me show you what I've done. With this small little function below, I can quickly get all the required information in no time. How do I use it? You can use it either via pipeline from Get-ChildItem or by providing full path inputs to it.

# Option 1
Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData -Signature | Out-HtmlView -ScrollX -Filtering -AllProperties

# Option 2
$Files = "$Env:USERPROFILE\Desktop\LAPS.x64.msi", "$Env:USERPROFILE\Desktop\DigiCertUtil.exe"
$Files | Get-FileMetaData -Signature | Out-HtmlView -ScrollX -Filtering -AllProperties

# Option 3
Get-FileMetaData -File $Files | Out-HtmlView -ScrollX -Filtering -AllProperties

# Option 4
Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData -Signature | Out-HtmlView -ScrollX -Filtering -AllProperties

This function has two parameters (for now) – one taking [System.IO.DirectoryInfo] (basically Get-ChildItem output) or [string], the other one is a switch deciding whether you want to check if the file is signed or not.

The output you see above is only output from two files. I'm specifically using the PSWriteHTML Out-HtmlView command with the ScrollX parameter to make sure it shows everything that there is to show. Additionally, please notice I'm using AllProperties switch. This switch is special when it comes to Out-HtmlView. This is because most of the commands such as Format-Table or Export-CSV or even Out-HtmlView, by default, display properties only from 1st entry in the array. It's efficient and fast. However, in the case of Get-FileMetaData, where each file has different properties it doesn't make much sense. Using AllProperties does a “prescan” of an Array, and then any property missing from 1st element is added so that Out-HtmlView can display everything there is to show. Of course, if you're working with the same file types that don't differ from each other, you can skip that parameter or Out-HtmlView for that matter. 

If you just want to peek into an object quickly, you can use Format-List * to see all parameters without using Out-HtmlView.

$Files = "$Env:USERPROFILE\Desktop\LAPS.x64.msi", "$Env:USERPROFILE\Desktop\DigiCertUtil.exe"
$Files | Get-FileMetaData -Signature | Format-List

If you intend to use Out-HtmlView for this, please make sure to install it first. You can do so using Install-Module

Install-Module PSWriteHTML -Force

Ok, so now that you know what you're getting into, there are two ways to get Get-FileMetaData function. Copy and paste from the source below or install one of my “do it all” modules called PSSharedGoods. I use the PSSharedGoods module as my glue module where multiple other modules use it as a dependency, or I copy the functions from it during build time and convert them to private services within other modules to minimize dependencies.

Install-Module PSSharedGoods -Force

Keep in mind that I update my modules quite often, fixing bugs, so always up to date versions of said modules can be downloaded from PowerShellGallery using Install-Module, as mentioned above. When I push my modules into PowerShellGallery, I minimize them and optimize them for production use. If you're into development and want to work in a more structured way where each function has separate files, multiple folders, and so on, I would highly recommend visiting my GitHub pages for PSWriteHTML or PSSharedGoods.

Getting file metadata with PowerShell

Here's the function in all it's glory for you to copy/paste and use as you see fit.

function Get-FileMetaData {
    <#
    .SYNOPSIS
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file

    .DESCRIPTION
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file

    .PARAMETER File
    FileName or FileObject

    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties

    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties

    .NOTES
    #>
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline)][Object] $File,
        [switch] $Signature
    )
    Process {
        foreach ($F in $File) {
            $MetaDataObject = [ordered] @{}
            if ($F -is [string]) {
                $FileInformation = Get-ItemProperty -Path $F
            } elseif ($F -is [System.IO.DirectoryInfo]) {
                #Write-Warning "Get-FileMetaData - Directories are not supported. Skipping $F."
                continue
            } elseif ($F -is [System.IO.FileInfo]) {
                $FileInformation = $F
            } else {
                Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F."
                continue
            }
            $ShellApplication = New-Object -ComObject Shell.Application
            $ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName)
            $ShellFile = $ShellFolder.ParseName($FileInformation.Name)
            $MetaDataProperties = [ordered] @{}
            0..400 | ForEach-Object -Process {
                $DataValue = $ShellFolder.GetDetailsOf($null, $_)
                $PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ', '')
                if ($PropertyValue -ne '') {
                    $MetaDataProperties["$_"] = $PropertyValue
                }
            }
            foreach ($Key in $MetaDataProperties.Keys) {
                $Property = $MetaDataProperties[$Key]
                $Value = $ShellFolder.GetDetailsOf($ShellFile, [int] $Key)
                if ($Property -in 'Attributes', 'Folder', 'Type', 'SpaceFree', 'TotalSize', 'SpaceUsed') {
                    continue
                }
                If (($null -ne $Value) -and ($Value -ne '')) {
                    $MetaDataObject["$Property"] = $Value
                }
            }
            if ($FileInformation.VersionInfo) {
                $SplitInfo = ([string] $FileInformation.VersionInfo).Split([char]13)
                foreach ($Item in $SplitInfo) {
                    $Property = $Item.Split(":").Trim()
                    if ($Property[0] -and $Property[1] -ne '') {
                        $MetaDataObject["$($Property[0])"] = $Property[1]
                    }
                }
            }
            $MetaDataObject["Attributes"] = $FileInformation.Attributes
            $MetaDataObject['IsReadOnly'] = $FileInformation.IsReadOnly
            $MetaDataObject['IsHidden'] = $FileInformation.Attributes -like '*Hidden*'
            $MetaDataObject['IsSystem'] = $FileInformation.Attributes -like '*System*'
            if ($Signature) {
                $DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.Fullname
                $MetaDataObject['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject
                $MetaDataObject['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer
                $MetaDataObject['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber
                $MetaDataObject['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore
                $MetaDataObject['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter
                $MetaDataObject['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint
                $MetaDataObject['SignatureStatus'] = $DigitalSignature.Status
                $MetaDataObject['IsOSBinary'] = $DigitalSignature.IsOSBinary
            }
            [PSCustomObject] $MetaDataObject
        }
    }
}

This post was last modified on June 20, 2020 18:43

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

1 week ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

5 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

7 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

8 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

8 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

9 months ago