Public/Get-AclItem.ps1

#!/usr/bin/env pwsh
Set-StrictMode -Version Latest

if ($IsWindows) {
    function Get-AclItem {
        <#
        .SYNOPSIS
            Gets ownership, access rules, and metadata for an item.
 
        .DESCRIPTION
            Reads the security descriptor of an item with Get-Acl and combines it with the
            item's file system metadata into one object. Hidden and system items are
            included. Audit rules (SACL) are not returned. Windows only.
 
        .PARAMETER LiteralPath
            The path of an existing item. The path is used exactly as typed, so wildcard
            characters such as [ and ] are not expanded. Accepts paths from the pipeline.
 
        .INPUTS
            System.String. You can pipe item paths to this function.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject. One object per item with the
            owner, group, access rules, SDDL, timestamps, and attributes.
 
        .EXAMPLE
            Get-AclItem -LiteralPath C:\Data
 
            Returns the item metadata and ACL details for C:\Data.
 
        .EXAMPLE
            'C:\Data', 'C:\Logs[2026]' | Get-AclItem
 
            Returns the ACL details for each piped path. The brackets are read literally.
 
        .EXAMPLE
            (Get-AclItem -LiteralPath C:\Data).Access | Where-Object -Property IsInherited -EQ $false
 
            Lists only the explicit, non-inherited access rules on C:\Data.
        #>

        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        param(
            [Parameter(Mandatory, ValueFromPipeline,
                HelpMessage = 'Existing item path, treated literally. Accepts path from the pipeline.')]
            [ValidateNotNullOrEmpty()]
            [string]$LiteralPath
        )
        process {
            if (-not (Test-Path -LiteralPath $LiteralPath)) {
                throw "Get-AclItem can not find literal path: $LiteralPath"
            }
            $acl = Get-Acl -LiteralPath $LiteralPath

            # Force is required. Test-Path and Get-Acl both see hidden and system items,
            # Get-Item does not, so the guard above would pass and this line would fail.
            $item = Get-Item -LiteralPath $LiteralPath -Force

            [PSCustomObject]@{
                FullName                = $item.FullName
                Name                    = $item.Name
                Path                    = $acl.Path
                ItemType                = $item.GetType().Name
                Owner                   = $acl.Owner
                Group                   = $acl.Group
                Access                  = $acl.Access | ForEach-Object {
                    [PSCustomObject]@{
                        FileSystemRights  = $_.FileSystemRights
                        AccessControlType = $_.AccessControlType
                        IdentityReference = $_.IdentityReference
                        IsInherited       = $_.IsInherited
                        InheritanceFlags  = $_.InheritanceFlags
                        PropagationFlags  = $_.PropagationFlags
                    }
                }
                CreationTime            = $item.CreationTime
                LastAccessTime          = $item.LastAccessTime
                LastWriteTime           = $item.LastWriteTime
                Mode                    = $item.Mode
                Attributes              = $item.Attributes
                Security                = $acl.Sddl
                AreAccessRulesCanonical = $acl.AreAccessRulesCanonical
                AreAuditRulesCanonical  = $acl.AreAuditRulesCanonical
                AreAccessRulesProtected = $acl.AreAccessRulesProtected
                AreAuditRulesProtected  = $acl.AreAuditRulesProtected
            }
            # TODO: expose $acl.Audit. Reading it needs SeSecurityPrivilege, which
            # elevation alone does not grant, so it wants its own -Audit switch.
        }
    }
}