Benjamin Hubbard
Benjamin Hubbard

Reputation: 2917

Get/set file attributes in PowerShell when the path\filename is longer than 260 characters

I am looking for a way to get and set file attributes (hidden and readonly) in PowerShell for files where the combined path and filename are longer than 260 characters. I know that the .NET classes don't support longer file paths; I've already tried that. Attrib doesn't work either. I get error "Parameter format not correct -". Likewise Dir doesn't work either.

I am using Robocopy to obtain the filenames. Robocopy has no issues with long paths. However, I can't use Robocopy to get/set attributes since I am just using the /L list mode of Robocopy.

Anyone have workarounds for PowerShell?

Update:

subst does not support extra-long paths. It does seem to work with partial paths though.

mklink requires local volumes.

net use does not support extra-long paths. It does seem to work with partial paths though.

New-PSDrive does not support extra-long paths, not even partial paths.

Upvotes: 5

Views: 1036

Answers (3)

user26571886
user26571886

Reputation: 21

You can modify file attributes in PowerShell by directly editing the attributes property of a file object ($file.Attributes), even where the combined path and filename are longer than 260 characters. For example, to get a file's attributes, you can use this function:

function GetFileAttributes {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )

    # Attribute constants
    $attributeMap = @{
        "ReadOnly"           = 0x00000001 # FILE_ATTRIBUTE_READONLY              - A file that is read-only
        "Hidden"             = 0x00000002 # FILE_ATTRIBUTE_HIDDEN                - A file that is hidden
        "System"             = 0x00000004 # FILE_ATTRIBUTE_SYSTEM                - A file or directory that the operating system uses a part of or exclusively
        "Directory"          = 0x00000010 # FILE_ATTRIBUTE_DIRECTORY             - The handle that identifies a directory
        "Archive"            = 0x00000020 # FILE_ATTRIBUTE_ARCHIVE               - Applications typically use this attribute to mark files for backup or removal
        "Device"             = 0x00000040 # FILE_ATTRIBUTE_DEVICE                - This value is reserved for system use
        "Normal"             = 0x00000080 # FILE_ATTRIBUTE_NORMAL                - A file that does not have other attributes set, valid only when used alone
        "Temporary"          = 0x00000100 # FILE_ATTRIBUTE_TEMPORARY             - A file that is being used for temporary storage
        "SparseFile"         = 0x00000200 # FILE_ATTRIBUTE_SPARSE_FILE           - A file that is a sparse file
        "ReparsePoint"       = 0x00000400 # FILE_ATTRIBUTE_REPARSE_POINT         - A file or directory that has an associated reparse point, or a file that is a symbolic link
        "Compressed"         = 0x00000800 # FILE_ATTRIBUTE_COMPRESSED            - A file or directory that is compressed
        "Offline"            = 0x00001000 # FILE_ATTRIBUTE_OFFLINE               - A file for which the data is not available immediately
        "NotContentIndexed"  = 0x00002000 # FILE_ATTRIBUTE_NOT_CONTENT_INDEXED   - A file or directory which is not to be indexed by the content indexing service
        "Encrypted"          = 0x00004000 # FILE_ATTRIBUTE_ENCRYPTED             - A file or directory that is encrypted
        "IntegrityStream"    = 0x00008000 # FILE_ATTRIBUTE_INTEGRITY_STREAM      - The directory or user data stream is configured with integrity (only supported on ReFS volumes)
        "Virtual"            = 0x00010000 # FILE_ATTRIBUTE_VIRTUAL               - This value is reserved for system use.
        "NoScrubData"        = 0x00020000 # FILE_ATTRIBUTE_NO_SCRUB_DATA         - The user data stream not to be read by the background data integrity scanner
        # "ExtendedAttributes" = 0x00040000 # FILE_ATTRIBUTE_EA                    - A file or directory with extended attributes. IMPORTANT: For internal use only. This is why it is commented out as it shares a number.
        "Pinned"             = 0x00080000 # FILE_ATTRIBUTE_PINNED                - Indicates user intent that the file or directory should be kept fully present locally even when not being actively accessed.
        "Unpinned"           = 0x00100000 # FILE_ATTRIBUTE_UNPINNED              - Indicates that the file or directory should not be kept fully present locally except when being actively accessed
        "RecallOnOpen"       = 0x00040000 # FILE_ATTRIBUTE_RECALL_ON_OPEN        - The file or directory has no physical representation on the local system
        "RecallOnDataAccess" = 0x00400000 # FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS - the file or directory is not fully present locally
    }

    # Get the current attributes
    $obj = Get-Item -Path $FilePath -Force
    $currentAttributes = $obj.Attributes

    # Convert attributes to their names
    $activeAttributes = $attributeMap.GetEnumerator() | Where-Object {
        $currentAttributes -band $_.Value
    } | ForEach-Object { $_.Key }

    if ($activeAttributes.Count -eq 0) {
        $activeAttributes = "Normal"
    }

    # Output the attributes
    $activeAttributes
}

And to set them I used this one:

function ModifyFileAttributes {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Add", "Remove", "Set", "ClearAll")]
        [string]$Action,

        [Parameter(Mandatory = $true)]
        [ValidateSet("ReadOnly", "Hidden", "System", "Directory",
            "Archive", "Normal", "Temporary", "SparseFile",
            "ReparsePoint", "Compressed", "Offline", "NotContentIndexed",
            "Encrypted", "IntegrityStream", "Virtual", "NoScrubData",
            "Pinned", "Unpinned", "RecallOnOpen", "RecallOnDataAccess")]
        [string[]]$Attributes,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Add", "Remove")]
        [string]$Action2,

        [Parameter(Mandatory = $false)]
        [ValidateSet("ReadOnly", "Hidden", "System", "Directory",
            "Archive", "Normal", "Temporary", "SparseFile",
            "ReparsePoint", "Compressed", "Offline", "NotContentIndexed",
            "Encrypted", "IntegrityStream", "Virtual", "NoScrubData",
            "Pinned", "Unpinned", "RecallOnOpen", "RecallOnDataAccess")]
        [string[]]$Attributes2
    )

    # Attribute constants
    $attributeMap = @{
        "ReadOnly"           = 0x00000001 # FILE_ATTRIBUTE_READONLY              - A file that is read-only
        "Hidden"             = 0x00000002 # FILE_ATTRIBUTE_HIDDEN                - A file that is hidden
        "System"             = 0x00000004 # FILE_ATTRIBUTE_SYSTEM                - A file or directory that the operating system uses a part of or exclusively
        "Directory"          = 0x00000010 # FILE_ATTRIBUTE_DIRECTORY             - The handle that identifies a directory
        "Archive"            = 0x00000020 # FILE_ATTRIBUTE_ARCHIVE               - Applications typically use this attribute to mark files for backup or removal
        "Device"             = 0x00000040 # FILE_ATTRIBUTE_DEVICE                - This value is reserved for system use
        "Normal"             = 0x00000080 # FILE_ATTRIBUTE_NORMAL                - A file that does not have other attributes set, valid only when used alone
        "Temporary"          = 0x00000100 # FILE_ATTRIBUTE_TEMPORARY             - A file that is being used for temporary storage
        "SparseFile"         = 0x00000200 # FILE_ATTRIBUTE_SPARSE_FILE           - A file that is a sparse file
        "ReparsePoint"       = 0x00000400 # FILE_ATTRIBUTE_REPARSE_POINT         - A file or directory that has an associated reparse point, or a file that is a symbolic link
        "Compressed"         = 0x00000800 # FILE_ATTRIBUTE_COMPRESSED            - A file or directory that is compressed
        "Offline"            = 0x00001000 # FILE_ATTRIBUTE_OFFLINE               - A file for which the data is not available immediately
        "NotContentIndexed"  = 0x00002000 # FILE_ATTRIBUTE_NOT_CONTENT_INDEXED   - A file or directory which is not to be indexed by the content indexing service
        "Encrypted"          = 0x00004000 # FILE_ATTRIBUTE_ENCRYPTED             - A file or directory that is encrypted
        "IntegrityStream"    = 0x00008000 # FILE_ATTRIBUTE_INTEGRITY_STREAM      - The directory or user data stream is configured with integrity (only supported on ReFS volumes)
        "Virtual"            = 0x00010000 # FILE_ATTRIBUTE_VIRTUAL               - This value is reserved for system use.
        "NoScrubData"        = 0x00020000 # FILE_ATTRIBUTE_NO_SCRUB_DATA         - The user data stream not to be read by the background data integrity scanner
        # "ExtendedAttributes" = 0x00040000 # FILE_ATTRIBUTE_EA                    - A file or directory with extended attributes. IMPORTANT: For internal use only. This is why it is commented out as it shares a number.
        "Pinned"             = 0x00080000 # FILE_ATTRIBUTE_PINNED                - Indicates user intent that the file or directory should be kept fully present locally even when not being actively accessed.
        "Unpinned"           = 0x00100000 # FILE_ATTRIBUTE_UNPINNED              - Indicates that the file or directory should not be kept fully present locally except when being actively accessed
        "RecallOnOpen"       = 0x00040000 # FILE_ATTRIBUTE_RECALL_ON_OPEN        - The file or directory has no physical representation on the local system
        "RecallOnDataAccess" = 0x00400000 # FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS - the file or directory is not fully present locally
    }


    if ($Action -eq "ClearAll") {
        if ($Action2) {
            Write-Output "Invalid action set"
            return
        }
        $obj.Attributes = [System.IO.FileAttributes]::Normal
        Write-Output "Attributes reset to normal."
        return
    }
    $Attributes = $Attributes | Select-Object -Unique

    # Get the current attributes
    $obj = Get-Item -Path $FilePath -Force
    $currentObjAttributes = $obj.Attributes

    # Convert attribute names to values
    $attrValues = 0
    foreach ($attr in $Attributes) {
        $attrValues = $attrValues -bor $attributeMap[$attr]
    }

    if ($Action2) {
        if ($Attributes2) {
            $Attributes2 = $Attributes2 | Select-Object -Unique
        }
        else {
            Write-Host "Action 2 given but no attribute given"
            return
        }
        switch ($Action) {
            "Add" {
                $attrValues = $currentObjAttributes -bor $attrValues
            }
            "Remove" {
                $attrValues = $currentObjAttributes -band -bnot $attrValues
            }
            "Set" {
                Write-Output "Invalid action set"
                return
            }
        }
        $attrValues2 = 0
        foreach ($attr in $Attributes2) {
            $attrValues2 = $attrValues2 -bor $attributeMap[$attr]
        }

        switch ($Action2) {
            "Add" {
                $obj.Attributes = $attrValues -bor $attrValues2
            }
            "Remove" {
                $obj.Attributes = $attrValues -band -bnot $attrValues2
            }
        }
        Write-Output "Attributes successfully updated to: $($obj.Attributes)"
        return
    }

    # Apply based on the specified action
    switch ($Action) {
        "Add" {
            $obj.Attributes = $currentObjAttributes -bor $attrValues
        }
        "Remove" {
            $obj.Attributes = $currentObjAttributes -band -bnot $attrValues
        }
        "Set" {
            $obj.Attributes = $attrValues
        }
    }

    Write-Output "Attributes successfully updated to: $($obj.Attributes)"
}

More details on file attribute constants can be found here and details on the extended value attribute, and why I chose to comment out it specifically despite there being many other dangerous attributes can be found here.

This works well with files with long names/paths and also has the added benefit of working easily with hidden files. Using the attrib command with hidden files requires you to explicitly mention the hidden attribute, even when not changing it but changing other attributes. This means that you do not need to check for whether the file is hidden or not before modifying attributes, improving code readability in most cases.

Upvotes: 0

nimizen
nimizen

Reputation: 3419

Building on vonPryz's answer and your idea to use partial paths, the following works although it's slow and requires error suppression:

subst m: 'Insert\a\complete\path\close\to\character\limit'
sleep 1
Push-Location 'm:\rest\of\path\to\the\file' -ErrorAction SilentlyContinue
Get-ChildItem | %{
attrib $_
}
subst m: /d
sleep 1

This answer also uses SUBST to get close to where we want to be; then, Push-Location sets the long file-names' parent directory as the 'current working directory' see here. Push-Location complains about it but it works anyway insofar as Get-ChildItem and ATTRIB appear to work with this 'current working directory' rather than parsing the entire path and as a result, ATTRIB works.

Upvotes: 0

vonPryz
vonPryz

Reputation: 24081

I guess using subst command to map the path as a drive letter is worth a shot. It is based on the olden days of DOS and still works on Winndows like so,

subst k: c:\some\really\complex\path\with\too\many\subdirs\and\suff\...

If subst doesn't work, try sharing a directory close to the file and access it via UNC path.

Upvotes: 1

Related Questions