Search code examples
regexpowershellif-statementwildcardnegation

Powershell: unexpected behavior of negated -like and -match conditionals


I have 2 folders in my windows folder, software, and softwaretest. So I have the main folder "software" if statement, then jump to the elseif - here I have the backup folder, so jump to the else...

my problem is that I'm getting the write-host from the elseif, and I have a backup folder that I'm calling softwaretest, so can't see why it give me that output and not the else.

hope someone can guide/help me :-)

    If ($SoftwarePathBackup = Get-ChildItem -Path "$Env:SystemRoot" | Where-Object { (!$_.Name -like 'software') }) {
        Write-Host ( 'There are no folder named \software\ on this machine - You cant clean/clear/empty the folder!' ) -ForegroundColor Red;
    } elseif ($SoftwarePathBackup = Get-ChildItem -Path "$Env:SystemRoot" | Where-Object { ($_.Name -match '.+software$|^software.+') } | Sort-Object) {
        Write-Host ( 'There are none folder-backups of \software\ on this machine - You need to make a folder-backup of \software\ before you can clean/clear/empty the folder!' ) -ForegroundColor Red;
    } else {
        Remove-Item
    }

Solution

  • Your primary problem is one of operator precedence:

    • !$_.Name -like 'software' should be ! ($_.Name -like 'software') or, preferably,
      $_.Name -notlike 'software' - using PowerShell's not-prefixed operators for negation.

    • Similarly, you probably meant to negate $_.Name -match '.+software$|^software.+' which is most easily achieved with $_.Name -notmatch '.+software$|^software.+'

    As stated in Get-Help about_Operator_Precedence, ! (a.k.a. -not) has higher precedence than -like, so !$_.Name -like 'software' is evaluated as (!$_.Name) -like 'software', which means that the result of !$_.Name - a Boolean - is (string-)compared to wildcard pattern 'software', which always returns $False, so the If branch is never entered.


    That said, you can make do without -like and -match altogether and use the implicit wildcard matching supported by Get-Item's -Include parameter (snippet requires PSv3+):

    # Get folders whose name either starts with or ends with 'software', including
    # just 'software' itself.
    $folders = Get-Item -Path $env:SystemRoot\* -Include 'software*', '*software' | 
                 Where-Object PSIsContainer
    
    # See if a folder named exactly 'software' is among the matches.
    $haveOriginal = $folders.Name -contains 'software'
    
    # See if there are backup folders among the matches (too).
    # Note that [int] $haveOriginal evaluates to 1 if $haveOriginal is $True,
    # and to 0 otherwise.
    $haveBackups = ($folders.Count - [int] $haveOriginal) -gt 0
    
    # Now act on $folders as desired, based on flags $haveOriginal and $haveBackups.
    
    • Note how Get-Item -Path $env:SystemRoot\* is used to explicitly preselect all items (add -Force if hidden items should be included too), which are then filtered down via -Include.

    • Since Get-Item - unlike Get-ChildItem- doesn't support -Directory, | Where-Object PSIsContainer is used to further limit the matches to directories (folders).

    • Note: Get-ChildItem was not used, because -Include only takes effect on child (descendant) items (too) when -Recurse is also specified; while -Recurse can be combined with -Depth 0 (PSv3+) in order to limit matching to immediate child directories, Get-ChildItem apparently still tries to read the entries of all child directories as well, which can result in unwanted access-denied errors from directories that aren't even of interest.
      In other words: Get-ChildItem -Recurse -Depth 0 -Directory $env:SystemRoot -include 'software*', '*software' is only equivalent if you have (at least) read access to all child directories of $env:SystemRoot.