Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Like behaviour on different Powershell versions

This code works well in Powershell 5+ but doesn't work in Powershell 4.0 and 2.0:

$DaysToDelete = 2

$targets = "profile/Default",
           "profile/Profile 1",
           "profile/Profile 2",
           "profile/Profile 3",
           "profile/Profile 4"

$special = @("chromium", "64")

$profiles = Get-ChildItem "C:\" -Directory -Force |
    Where-Object Name -In $special |
    Select-Object -ExpandProperty FullName

$chromeDir = "C:\Users\*\AppData\Local\Google\Chrome\User Data\Default"
$chromeSetDir = "C:\Users\*\Local Settings\Application Data\Google\Chrome\User Data\Default"

$Items = @("*Archived History*",
            "*Cache*",
            "*Cookies*",
            "*History*",
            "*Top Sites*",
            "*Visited Links*",
            "*Web Data*")

$profiles | ForEach-Object {
    foreach($target in $targets) {
        $profile = Join-Path $_ $target

        $items | ForEach-Object {
        $item = $_ 
        Get-ChildItem $profile, $chromeDir, $chromeSetDir -Recurse -Force -ErrorAction SilentlyContinue |
            Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete))  -and $_.Directory -like $item} | ForEach-Object { 
            $path = Join-Path $_.DirectoryName $_
            Remove-Item $path -force -Verbose -recurse -ErrorAction SilentlyContinue }
         }

    }
}

I revealed that the piece which breaks the execution is

-and $_.Directory -like $item

It works fine on PS 5+ (Windows 10) but finds nothing alike pattern on PS 4 (Windows 7). Chrome version and its directory hierarchy are the same on both machines: 59.0.3071.115 (Official Build) (64-bit).

Starting script on Win10 with version specification alike

powershell.exe -Version 4.0

gave nothing, it ran fine anything. I am not so fluent in Powershell version-specifics, so gurus are welcomed to propose any suggestions. How to make the script version-independent?

UPDATE: Here is the full code, but it gives nothing valuable. I verified all the places and exactly localized that problem line is the above.

Another interesting moment: I discovered that problem is not in the like clause per se, but in the combination of like and $_.CreationTime check:

$_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete) -and $_.Directory -like $item

If I put either of these conditions by itself, all is working fine, but if I combine them into single compound condition, nothing is returned, though there are folders that meet both conditions.

I cannot explain this in any way.

like image 271
Suncatcher Avatar asked Jul 24 '17 06:07

Suncatcher


2 Answers

I mentioned this in the comments; am not sure if it was clear enough, and whether this is the whole issue.

As per your original question, this line will not work. This is not dependent on the PowerShell version.

$_.Directory -like $item
  • When Get-ChildItem finds files, it will return a System.IO.FileInfo class object.

    • Directory is a property of the FileInfo class, so files will have this property.
    • Directory is of the type DirectoryInfo.
  • When Get-ChildItem finds folders/directories, it will return a System.IO.DirectoryInfo class object.

    • DirectoryInfo objects do not have a Directory property.
    • They have Parent, which also returns a System.IO.DirectoryInfo object
  • In either case, you are dealing with an object and comparing it to a string. When really you probably want to compare the folder Name to a string.
    Edit: True for Windows 8.1 running v5.1.14409.1005; expect to be the same for older OSes.
    False for Windows 10 running PS v5.1.143993.1480; expect to be the same on Server 2016. Not sure how the Name/FullName property is being automagically evaluated...


Get-ChildItem -File      | Where {$_.Directory      -like "*test*"}   # not ok: comparing object to string.
Get-ChildItem -Directory | Where {$_.Directory.Name -like "*test*"}   # not ok: DirectoryInfo object does not have Directory property

Get-ChildItem -File      | Where {$_.Directory.Name -like "*test*"}   # ok
Get-ChildItem -Directory | Where {$_.Parent.Name    -like "*test*"}   # ok
like image 119
G42 Avatar answered Oct 06 '22 22:10

G42


How to make the script version-independent?

For me the best way how to make it version-independent is to use the lowest version you need to support, which in your case is 2.0.

The other option is to detect the PowerShell version and make your version independent from each other.

I went through your script and I recommend using Test-Path -Path "$var" facility to test validity of the findings before actually removing the item.

Now to actually work on your issue: I have had similar issue when using PowerShell and clearning user profiles.

Adjusting for your particular case (circumventing your issue with like):

$newer_file_exist += Get-ChildItem -Path $profile -Recurse -Force -ErrorAction SilentlyContinue | Where-Object {$_.PSIsContainer -eq $FALSE} | where {($_.CreationTime).ToString('yyyy-MM-dd') -lt (get-date).adddays(-$DaysToDelete).ToString('yyyy-MM-dd')};

It will select all the files that are newer than -$DaysToDelete. It will recurse and select only files {$_.PSIsContainer -eq $FALSE}. If you want to select directory `{$_.PSIsContainer -eq $TRUE}.

# get all files to be deleted
ForEach ($dir in $profiles_with_path) {
    # to check
    $test_current_pathPath = Test-Path -Path $dir
    If ($test_current_pathPath) {
        #write-host 'Currently writing for these months:'$($time.Name);
        $files_to_delete += Get-ChildItem -Path $dir -recurse -Force | Where-Object {$_.PSIsContainer -eq $FALSE} | % { $_.FullName }
    }
}

To delete files:

  If ($files_to_delete) {
            ForEach ($file in $files_to_delete) { 
                #Remove-Item $file -Recurse -Force -ErrorAction SilentlyContinue
                Remove-Item $file -Force -ErrorAction SilentlyContinue
                If ($? -eq $true) {
                    $files_deleted ++;
                    #Write-Verbose -Verbose "$File deleted successfully!"
                }
            }

and

all the directories

ForEach ($dir in $profiles_with_path) { #
    Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue
    If ($? -eq $true) {
        $directories_deleted ++;
        #Write-Verbose -Verbose "$File deleted successfully!"
    }
}

These snippets work for 2.0 (tested on WinXP and Windows 2003 (SPx) and 4.0 Win7 tested.

First EDIT:

I tried to pick the valid codes for you, but apparently I could do better job. The logic was the following: I wanted to delete all profiles that did not contain any files newer than 3 months.

The variable $newer_file_exist - was used to indicate if such file was found. If it was found the whole profile was skipped - added to $excluded_directories.Add($profile) (for more see the source code link)

$profiles_with_path was populated by - $profiles_with_path = Get-ChildItem -Path $folder_to_cleanse -exclude $excluded_directories | Where-Object {$_.PSIsContainer -eq $True} where I have excluded all directories from $excluded_directories

This is my script which I have made public on my BB: The whole source code on my BB (it includes logic for test run, time specification, etc.)

like image 27
tukan Avatar answered Oct 06 '22 21:10

tukan