Search code examples
powershellwildcardstring-matching

Powershell Question How to Select Specific Characters in a File's Name?


I'm trying to create a Powershell script that looks for just files with the extension .dgn within a specific directory. Then if it has a character string of "_ch_" in the name of the file it will delete it. Else it will move the file to a new directory.

        Function Remove-ExtraneousCADFiles {
    [CmdletBinding()]
    
    Param (
    [Parameter(ValueFromPipeline = $true)]
        $paths
    )
    
    Begin {Clear-Host}
    
    Process {
    $char = "_ch_"
    $fileName = Split-Path $input -leaf
    $destDir = "D:\CRASH\ACCID - Lite Test\"
    Write-Output ("fileName= " + $fileName)
    Write-Output ("char= " + $char)
    Write-Output ("_.Name= " + $_.Name)
    
    #if ($_.contains($char)) {
    
    #if (Where $_.Name -Match $char) {
    
    #if ($_.Name -Match $char) {
    
    if ($_.Name -Like $char) {
    Remove-Item $_ -Force -Confirm -WhatIf
    Write-Output ("Delete= " + $fileName.Name)
    }
    else {
    #Write-Output ("Move _.FullName= " + $_.FullName)
    #Write-Output ("destDir= " + $destDir)
    #Write-Output ("_.BaseName= " + $_.BaseName)
    #Write-Output ("_.Extension= " + $_.Extension)
    #Write-Output ""
    Move-Item -Path $_.FullName -Destination ($destDir + $_.BaseName + $_.Extension) -WhatIf
    Write-Output ""
    }
    }
    
    End {}
    
    }

$inputPath = "D:\CRASH\ACCID - Lite Test zzz"
(Get-ChildItem -Path $inputPath -Recurse -Include *.dgn) | Remove-ExtraneousCADFiles

But I can't get the "IF" portion of my script to pass anything to the Remove-Item or Write-Output command. I've try other commands but I'm not having any luck, any help would be greatly appreciated.

These are an example of the files I'm trying to weed out to either delete or move: enter image description here

Thanks, G



Solution

  • $_.Name -Like $char

    -like uses wildcard expressions, which must match the input as a whole.

    Therefore, enclose your substring in *...*:

    $_.Name -like "*$char*"
    

    Alternatively use -match, which matches part of the input by default (substring matching):

    $_.Name -match $char
    

    However, note that -match uses regular expressions (regexes), so if your - designed to be used verbatim - search string happens to contain regex metacharacters (e.g., ., +), you'll have to escape them (not necessary in your case):

    $_.Name -match [regex]::Escape($char)
    

    Note:

    • Direct use of .NET APIs also allows you to use the [string] type's. .Contains() method, which uses case-exact substring matching by default.

      • That is, $_.Name.Contains($char) does work for case-exact matching.
    • However, this is at odds with PowerShell's fundamentally case-insensitive nature:

      • invariably so in Windows PowerShell: there is no overload with a case-insensitivity opt-in.

      • in PowerShell (Core) 7+ you can use a newly introduced overload of .Contains() that permits case-insensitive matching on an opt-in basis, via an additional parameter, e.g.:

        # PowerShell (Core) v7+ only
        'foo'.Contains('OO', 'InvariantCultureIgnoreCase') # -> $true
        
    • While direct use of .NET APIs is a viable option - which is a testament to PowerShell's versatility - the flip side is that doing so is not PowerShell-idiomatic, for two reasons:

      • By default, .NET APIs are case-sensitive , whereas PowerShell is case-insensitive.

      • Calling .NET APIs:

        • requires method syntax (enclosure of arguments in (...), separating arguments with , - e.g, [double]::Parse('1.2', [cultureinfo]::InvariantCulture)), which contrasts with PowerShell's shell-like argument-parsing mode (e.g., Get-Date -Year 1963) as well as the use of PowerShell's operators in expression-parsing mode (e.g; 1.2 + 1)

        • requires passing full file paths to .NET APIs, given that .NET's notion of the current directory usually differs from PowerShell's - see this answer.