Replace invalid filename chars

Viewed 230

I want to write a small utility function that replaces any sequence of forbidden chars in filename by a dash

For example :

  • foo.txt ==> foo.txt
  • Some string \o/ ==> Some string -o-
  • https://stackoverflow.com/questions ==> https-stackoverflow.com-questions

I write the function like this:

function Get-SafeFileName{
    param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [object]$Data
    )
    process {
    
        $pattern = "[" + [regex]::Escape([string][System.IO.Path]::GetInvalidFileNameChars()) +"]+"

        [regex]::Replace($Data, $pattern, "-")
    }
}

This is working, except the space character is replaced even it's an allowed character.

This is a string results in This-is-a-string which isn't necessary.

How to fix that?

Digging a bit shows that [System.IO.Path]::GetInvalidFileNameChars() doesn't contains the space char (ascii code 32). But there are many other "space" alike chars.

Maybe the regex engine doesn't see the difference?

2 Answers

First, you convert the list of invalid chars incorrectly by casting it to a string, that is where the spaces appear in the character class.

Second, you can't use Regex.Escape to escape chars that go to a character class as it is meant to escape the characters that must be literal outside a character class.

The fix is

function Get-SafeFileName{
    param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [object]$Data
    )
    process {
    
        $pattern = '[' + ([System.IO.Path]::GetInvalidFileNameChars() -join '').Replace('\','\\') + ']+'

        [regex]::Replace($Data, $pattern, "-")
    }
}

The only characters that need to be escaped inside a character class are:

  • ^
  • -
  • \
  • ].

Since the GetInvalidFileNameChars() only contains one of the four special chars mentioned, you can just use a single .Replace('\', '\\') instead of all four .Replace('\','\\') .Replace('-','\-').Replace('^','\^').Replace(']','\]').

I found another way using unicode construction

function Get-SafeFileName{
    param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [object]$Data
    )
    process {
    
        $pattern = "[" + ( ([System.IO.Path]::GetInvalidFileNameChars() | % { "\x" + ([int]$_).ToString('X2') } ) -join '') +"]+"

        [regex]::Replace($Data, $pattern, "-")
    }
}

$pattern is now [\x22\x3C\x3E\x7C\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x3A\x2A\x3F\x5C\x2F]+

With that, there's no ambiguity. Standard spaces aren't replaced

Related