6

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.

apaderno
  • 28,547
  • 16
  • 75
  • 90
Suncatcher
  • 10,355
  • 10
  • 52
  • 90
  • 1
    To check the version of the PowerShell engine being ran, [check out this post](https://stackoverflow.com/questions/1825585/determine-installed-powershell-version?rq=1). I suspect you're running `2.0` on the Windows 7 machine. – G42 Jul 24 '17 at 06:41
  • @gms0ulman, no, in console it reports `4 0 -1 -1` like in those post. Can PS engine versions vary in terminal and while running scripts in ISE/Task scheduler? – Suncatcher Jul 24 '17 at 06:45
  • 3
    No they will be the same. [Unless `-Version 2` is ran](https://msdn.microsoft.com/en-us/powershell/scripting/setup/starting-the-windows-powershell-2.0-engine) ([`-Version 4.0` does not launch the v4 engine](https://stackoverflow.com/questions/44162229/cant-change-powershell-version)) – G42 Jul 24 '17 at 07:11
  • Thx for useful hint. Yes, this addition works only for 2nd version. And yes, the above code doesn't work in Win10 as well with the `-version 2.0`. – Suncatcher Jul 24 '17 at 08:29
  • So it seems, that this piece doesn't work neither in 2.0, nor in 4.0. – Suncatcher Jul 24 '17 at 09:19
  • 2
    I'm stumped. AFAICS your code shouldn't differ between `4.0` & `5.0`; `$_.Directory` is a property of [`FileInfo`](https://msdn.microsoft.com/en-us/library/system.io.fileinfo(v=vs.110).aspx). is not PS version specific. It's not part of [`DirectoryInfo`](https://msdn.microsoft.com/en-us/library/system.io.directoryinfo(v=vs.110).aspx); `$_.Parent.Name` is way to go. Apologies if this is stuff you already know. Suggestion: update your question with more description/ stepping through to see if there is an issue elsewhere. Reg `2.0`: no support for `-Directory`, `-in` or the short form of `Where` – G42 Jul 24 '17 at 09:27
  • Added update with the code – Suncatcher Jul 24 '17 at 10:56
  • version 2.0 will have issues with the `Where-Object` syntax without `{}`, the `Get-ChildItem -Directory` flag and the `-in` operator. I believe all of those came with 3.0 – BenH Sep 05 '17 at 22:10
  • The question was not to acknowledge the problem but to resolve it :) To make the code version-independent. – Suncatcher Sep 06 '17 at 04:01
  • Because you are using `Where-object`, what happens if instead of the `-and` statement, you break up the statement into two? `?{$_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete)} | ?{$_.Directory -like $item} ` – Maxime Franchot Sep 06 '17 at 08:20
  • You can also use it to check what gets filtered where, but I am guessing you did that anyways. – Maxime Franchot Sep 06 '17 at 08:21
  • @MaximeFranchot, doesn't work. – Suncatcher Sep 06 '17 at 08:32
  • In that case, we know it's one of the filters (possibly bugging), and not the `-and`. – Maxime Franchot Sep 06 '17 at 08:50

2 Answers2

4

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
G42
  • 9,791
  • 2
  • 19
  • 34
  • I cannot agree. Here is my test results I made just now: Win10: **works** both via `$_.Directory.Name` and `$_.Directory`, Win7: does **not** work in either way. – Suncatcher Jul 25 '17 at 09:59
  • Try the full script from Pastebin on your files. – Suncatcher Jul 25 '17 at 10:00
  • 1
    @Suncatcher Thanks for the feedback. The code is too much for me, would be happy to try a [mcve](https://stackoverflow.com/help/mcve). I've tried a few Cmdlets on Win10 and can see no need to explicitly refer to `.Name` property. However still get no `Directory` property for folders, only `Parent`. Are you expecting to find files or folders? – G42 Jul 25 '17 at 10:24
  • I expect both. However in this concrete case It founds only files, 'cause the folder it gets `Get-ChildItem` from is *C:\chromium\profile\Default\Cache* which was matched by `*Cache*` pattern, and this dir doesn't have subdirs. And suppose it is, I assume, it will find them too, 'cause child command has `-Recurse` directive. – Suncatcher Jul 25 '17 at 10:29
  • `-recurse` only applies any filter on the first child, not on any children. It's a very strange implementation choice, but it does something completely different than you're expecting.To filter every directory of file under a certain folder, you need to implement your own recursion or filter in `.FullPath` instead of relying on `-Recurse`. – jessehouwing Sep 06 '17 at 10:51
  • 1
    @jessehouwing: That only applies if you combine `-Recurse` with `-Path` (the implicit 1st positional parameter); if use `-Path` to specify the target dir and specify the pattern via `-Filter` or `-Include`, filtering happens on every level of the target dir. hierarchy, as desired. – mklement0 Sep 06 '17 at 12:38
2

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.)

tukan
  • 17,050
  • 1
  • 20
  • 48
  • It's hard to bring all your snippets together. How to compile them into similar piece like mine, which deletes all the files and folders older than X days? I don't see where `$newer_file_exist` var is used and how `$profiles_with_path` var is populated – Suncatcher Sep 07 '17 at 16:01
  • @Suncatcher: Please see the edit. I have answered your question and provided the whole source code. – tukan Sep 08 '17 at 07:33
  • Well, your whole script is over-complicated for me. I applied just time condition to `($_.CreationTime).ToString('yyyy-MM-dd') -gt (get-date).adddays($time_definition.$($time.Name)).ToString('yyyy-MM-dd')` and it seems doesn't work. It just selects all the files disregard which number I put into `$time_definition` – Suncatcher Sep 13 '17 at 18:09
  • @Suncatcher: I was on off-line vacation. I think you did not grasp fully the script. The definition of the hash `$time_definition` is `@{'3m'="-90"};`. Which defines the period for which the `ForEach` will iterate (`ForEach($time in time_definition.GetEnumerator()`). So if you don't want to iterate over such hash (times) it maybe enough for you to do: `($_.CreationTime).ToString('yyyy-MM-dd') -gt (get-date).adddays($time).ToString(‌​'yyyy-MM-dd')`, where e.g. $time="-90" (the timeframe in days you want to subtract). – tukan Sep 24 '17 at 13:04
  • @Suncatcher don't forget that you have `-gt` whereas in your implementation you have `-lt`. – tukan Sep 25 '17 at 08:51