Check Modify Age of 100k files --> PathTooLongException - powershell

currently I am working on a script that should give me the number of files that have been modified in the last 30 days or between 30 and 180 days or that have not been modified for 180 days. So three queries in total, everything gets its own variable in which the count is stored.
The whole thing should be executed on several remote servers.
However, the folder structure on these servers is very deep and PowerShell regularly hits the 260 character limit (PathTooLongException).
The following is the script:
$servers = "server1", "server2"
$folder = "\Path\to\folder\"
foreach ($server in $servers) {
$session = New-PSSession -ComputerName $server
$folder_30 = Invoke-Command -Session $session -ScriptBlock {
(Get-ChildItem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -ge (Get-Date).AddDays(-30)}).count
}
$folder_30_180 = Invoke-Command -Session $session -ScriptBlock {
(Get-Childitem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -ge (Get-Date).AddDays(-180) -AND $_.LastWriteTime -lt (Get-Date).AddDays(-30)}).count
}
$folder_180 = Invoke-Command -Session $session -ScriptBlock {
(Get-Childitem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -lt (Get-Date).AddDays(-180)}).count
}
Remove-PSSession $session
}
Is there a way to change the script so that the "PathTooLongException" error no longer occurs? Would it be an option to change the script so that it first jumps to the lower levels and only then executes the "Get-ChildItem" and count cmdlt and how would I do that? Or is there a more elegant solution?
Thanks in advance :)
Edit: the remote servers are running win server 2012
Edit 2: I found a solution which is not perfect and takes like twice as long, but I can avoid the PathTooLongException;
You can use the following code snippet:
Get-Childitem -LiteralPath \\?\UNC\SERVERNAME\folder\
This didn't work with the Invoke-Command on the remote servers because they are running an old PowerShell version, but I was able to use this command and remotely connect from a Win 2016. Not perfect, but it works ¯_(ツ)_/¯
This link helped me: https://c-nergy.be/blog/?p=15339

You could probably do faster using Robocopy.
Suppose the local path on each server is 'C:\Somewhere\folder', then for each server you need to use the UNC path \\servername\C$\Somewhere\folder.
The easiest way of doing that is to create a template folder path where only the servername needs to be inserted:
$servers = "server1", "server2"
$folder = '\\{0}\C$\Somewhere\folder' # template UNC path whwere te servername or IP is inserted
foreach ($server in $servers) {
$path = $folder -f $server
$allFiles = #((robocopy $path NULL /L /E /NP /XJ /NC /NS /NDL /NJH /NJS /TS /R:0 /W:0) -replace '^\s+' -ne '' |
Select-Object #{Name = 'LastWriteTime'; Expression = {([datetime]($_ -split "`t")[0]).ToLocalTime()}},
#{Name = 'FullName'; Expression = {($_ -split "`t")[1]}})
# get the number of files newer than 30 days ago
$date = (Get-Date).AddDays(-30).Date
$folder_30 = ($allFiles | Where-Object { $_.LastWriteTime -ge $date }).Count
# get the number of files older than 180 days ago
$date = (Get-Date).AddDays(-180).Date
$folder_180 = ($allFiles | Where-Object { $_.LastWriteTime -lt $date }).Count
# output an object with the properties needed
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
Path = $path
AllFiles = $allFiles.Count
NewerThan30Days = $folder_30
OlderThan180Days = $folder_180
Between30And180Days = $allFiles.Count - $folder_30 - $folder_180
}
}

Related

PSCustomObject Check Multiple Computers for folder

I am trying to check for presence of a particular folder "appdata\Local\Packages\ActiveSync" in each of the profile folders that are returned for each of the computer by the below script.Searching through various forums I got the script below and need further assistance to eventually output it to a file with results of Test-Path against each computer name and corresponding profile path.
e.g. \\Computer1\C:\users\John\appdata\Local\packages\ActiveSync False
Invoke-Command -Computer (get-content c:\temp\servers.txt) -ScriptBlock {
Get-childItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' |
% {Get-ItemProperty $_.pspath }} | Select pscomputername,profileimagepath |
Where-Object { $_.ProfileImagePath -like "C:\users*" } | Out-File c:\temp\profiles.csv
For this, I think I would use a loop to go through all user path strings like below:
Invoke-Command -ComputerName (Get-Content -Path 'c:\temp\servers.txt') -ScriptBlock {
$regPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*'
Get-ItemPropertyValue -Path $regPath -Name 'ProfileImagePath' -ErrorAction SilentlyContinue |
Where-Object { $_ -like 'C:\Users*' } | ForEach-Object {
# combine the value with the rest of the path to form a LOCAL path
$path = Join-Path -Path $_ -ChildPath 'AppData\Local\Packages\ActiveSync'
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
Path = '\\{0}\{1}' -f $env:COMPUTERNAME, ($path.Replace(":", "$")) # UNC format
Exists = Test-Path -Path $path -PathType Container
}
}
} | Export-Csv -Path 'c:\temp\profiles.csv' -NoTypeInformation
Please note that if the output should be a structured CSV file, you need to use Export-Csv on the resulting objects instead of Out-File.
Also you may need to append parameter -Credential to the Invoke-Command call where you can give it administrative credentials.

Delete certain type of files when disk usage is below a treshold

I have a powershell script that runs everyday as a scheduledjob and deletes any logs over 30 days old. However, I want for the script to also delete these same files if there is less than 5 GB free disk space. Any ideas what would be the best approach to that?
Here's how the script currently looks:
Unregister-ScheduledJob -Name LogRotation -ErrorAction SilentlyContinue
wevtutil set-log Microsoft-Windows-TaskScheduler/Operational /enabled:true
$T = New-JobTrigger -Daily -At "12:37 PM" -DaysInterval 1
$O = #{
WakeToRun=$true
StartIfNotIdle=$false
MultipleInstancePolicy="Queue"
}
$SB = Get-ChildItem "C:\Folder\*" -Recurse -Include *.log | Where-Object {($_.LastWriteTime -lt (Get-Date).AddDays(-30))} | Remove-Item
Register-ScheduledJob -Trigger $T -ScheduledJobOption $O -Name CTLogRotation -ScriptBlock {$SB}
You could first check the remaining disk space. Then create a different scriptblock when that free space is less than 5Gb
if (((Get-Volume -DriveLetter C).SizeRemaining / 1Gb) -lt 5) {
# delete all log files if the remaining size in GB is less than 5
$SB = Get-ChildItem "C:\Folder" -Recurse -Filter '*.log' -File | Remove-Item -Force
}
else {
# delete all log files older than 30 days
$SB = Get-ChildItem "C:\Folder" -Recurse -Filter '*.log' -File | Where-Object {($_.LastWriteTime -lt (Get-Date).AddDays(-30).Date)} | Remove-Item -Force
}
P.S. I'm using -Filter '*.log', because that is way faster than -Include.
-Filter can however have just one file pattern.
I also added switch -File, so the code does not look for directories

Searching with Get-ChildItem over the network

I'm having an issue while searching with PowerShell over the network; program get stuck while executing Get-ChildItem.
# creating search string
$date = "*2018-01-10*"
$format = ".avi"
$toSearch = $date + $format
echo $toSearch
# Verifying A: drive to be disconnected
net use /d A: /y
# connecting Network drive to local drive A:
if (Test-Connection -ComputerName PROC-033-SV -Quiet) {net use A: \\PROC-033-SV\c$}
# getting list of directories to search on
$userList = Get-ChildItem -Path A:\users\*.* -Directory
# verifying list of directories prior to search
echo $userList
# searching through Network, on A:\Users\ subdirectories for $toSearch variable
Get-ChildItem -Path $userList -Include $toSearch -Recurse -Force
# *** HERE's where the program get stuck, it nevers stop searching
# it nevers reach pause
pause
Does anyone know why Get-ChildItem keeps looping and it never stops?
I'm using PS v4 no -Depth option available for -Recurse parameter; I'm suspecting that might be the issue.
If you want to limit recursion depth in PowerShell v4 and earlier you could wrap Get-ChildItem in a custom function, e.g. like this:
function Get-ChildItemRecursive {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[string[]]$Path = $PWD.Path,
[Paramter(Mandatory=$false)]
[string]$Filter = '*.*',
[Parameter(Mandatory=$false)]
[int]$Depth = 0
)
Process {
Get-ChildItem -Path $Path -Filter $Filter
if ($Depth -gt 0) {
Get-ChildItem -Path $Path |
Where-Object { $_.PSIsContainer } |
Get-ChildItemRecursive -Filter $Filter -Depth ($Depth - 1)
}
}
}

Files not getting deleted from remote servers

I have written the following script using a post on this forum. The script deletes the files which are older than 15 days:
cls
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock {
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
}
The script is executing fine, but no files are getting deleted.
As already mentioned in the comments, you need to pass the parameters to use inside the scriptblock as arguments to the -ArgumentList parameter:
$RemoveOldFiles = {
param(
[string]$path,
[datetime]$limit
)
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock $RemoveOldFiles -ArgumentList $path,$limit
}
Remove-Item does not exist in PowerShell version 1.0 - make sure your destination servers have v2.0 or better installed.

Delete old files in recycle bin with powershell

Ok, I have a script I am writing in powershell that will delete old files in the recycle bin. I want it to delete all files from the recycle bin that were deleted more than 2 days ago. I have done lots of research on this and have not found a suitable answer.
This is what I have so far(found the script online, i don't know much powershell):
$Path = 'C' + ':\$Recycle.Bin'
Get-ChildItem $Path -Force -Recurse -ErrorAction SilentlyContinue |
#Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-3) } |
Remove-Item -Recurse -exclude *.ini -ErrorAction SilentlyContinue
It is working great with one exception, it checks the file parameter "LastWriteTime". That is awesome if the user deletes the file they same day they modify it. Otherwise it fails.
How can I modify this code so that it will check when the file was deleted, not when it was written.
-On a side note, if I run this script from an administrator account on Microsoft Server 2008 will it work for all users recycle bins or just mine?
Answer:
the code that worked for me is:
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e",""
$dtDeletedDate = get-date $DeletedDate
If($dtDeletedDate -lt (Get-Date).AddDays(-3))
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
}#EndIF
}#EndForeach item
It works awesome for me, however 2 questions remain...How do I do this with multiple drives? and Will this apply to all users or just me?
WMF 5 includes the new "Clear-RecycleBin" cmdlet.
PS > Clear-RecycleBin -DriveLetter C:\
These two lines will empty all the files recycle bin:
$Recycler = (New-Object -ComObject Shell.Application).NameSpace(0xa)
$Recycler.items() | foreach { rm $_.path -force -recurse }
This article has answers to all your questions
http://baldwin-ps.blogspot.be/2013/07/empty-recycle-bin-with-retention-time.html
Code for posterity:
# -----------------------------------------------------------------------
#
# Author : Baldwin D.
# Description : Empty Recycle Bin with Retention (Logoff Script)
#
# -----------------------------------------------------------------------
$Global:Collection = #()
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
$csvfile = "\\YourNetworkShare\RecycleBin.txt"
$LogFailed = "\\YourNetworkShare\RecycleBinFailed.txt"
function Get-recyclebin
{
[CmdletBinding()]
Param
(
$RetentionTime = "7",
[Switch]$DeleteItems
)
$User = $env:USERNAME
$Computer = $env:COMPUTERNAME
$DateRun = Get-Date
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e","" #Invisible Unicode Characters
$DeletedDate_datetime = get-date $DeletedDate
[Int]$DeletedDays = (New-TimeSpan -Start $DeletedDate_datetime -End $(Get-Date)).Days
If($DeletedDays -ge $RetentionTime)
{
$Size = $Recycler.GetDetailsOf($item,3)
$SizeArray = $Size -split " "
$Decimal = $SizeArray[0] -replace ",","."
If ($SizeArray[1] -contains "bytes") { $Size = [int]$Decimal /1024 }
If ($SizeArray[1] -contains "KB") { $Size = [int]$Decimal }
If ($SizeArray[1] -contains "MB") { $Size = [int]$Decimal * 1024 }
If ($SizeArray[1] -contains "GB") { $Size = [int]$Decimal *1024 *1024 }
$Object = New-Object Psobject -Property #{
Computer = $computer
User = $User
DateRun = $DateRun
Name = $item.Name
Type = $item.Type
SizeKb = $Size
Path = $item.path
"Deleted Date" = $DeletedDate_datetime
"Deleted Days" = $DeletedDays }
$Object
If ($DeleteItems)
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
if ($?)
{
$Global:Collection += #($object)
}
else
{
Add-Content -Path $LogFailed -Value $error[0]
}
}#EndIf $DeleteItems
}#EndIf($DeletedDays -ge $RetentionTime)
}#EndForeach item
}#EndFunction
Get-recyclebin -RetentionTime 7 #-DeleteItems #Remove the comment if you wish to actually delete the content
if (#($collection).count -gt "0")
{
$Collection = $Collection | Select-Object "Computer","User","DateRun","Name","Type","Path","SizeKb","Deleted Days","Deleted Date"
$CsvData = $Collection | ConvertTo-Csv -NoTypeInformation
$Null, $Data = $CsvData
Add-Content -Path $csvfile -Value $Data
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)
#ScriptEnd
Had to do a bit of research on this myself, the recycle bin contains two files for every file deleted on every drive in win 10 (in win 7 files are as is so this script is too much and needs to be cut down, especially for powershell 2.0, win 8 untested), an info file created at time of deletion $I (perfect for ascertaining the date of deletion) and the original file $R, i found the com object method would ignore more files than i liked but on the up side had info i was interested in about the original file deleted, so after a bit of exploring i found a simple get-content of the info files included the original file location, after cleaning it up with a bit of regex and came up with this:
# Refresh Desktop Ability
$definition = #'
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
public static void Refresh() {
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
}
'#
Add-Type -MemberDefinition $definition -Namespace WinAPI -Name Explorer
# Set Safe within deleted days and get physical drive letters
$ignoreDeletedWithinDays = 2
$drives = (gwmi -Class Win32_LogicalDisk | ? {$_.drivetype -eq 3}).deviceid
# Process discovered drives
$drives | % {$drive = $_
gci -Path ($drive+'\$Recycle.Bin\*\$I*') -Recurse -Force | ? {($_.LastWriteTime -lt [datetime]::Now.AddDays(-$ignoreDeletedWithinDays)) -and ($_.name -like "`$*.*")} | % {
# Just a few calcs
$infoFile = $_
$originalFile = gi ($drive+"\`$Recycle.Bin\*\`$R$($infoFile.Name.Substring(2))") -Force
$originalLocation = [regex]::match([string](gc $infoFile.FullName -Force -Encoding Unicode),($drive+'[^<>:"/|?*]+\.[\w\-_\+]+')).Value
$deletedDate = $infoFile.LastWriteTime
$sid = $infoFile.FullName.split('\') | ? {$_ -like "S-1-5*"}
$user = try{(gpv "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\$($sid)" -Name ProfileImagePath).replace("$(gpv 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList' -Name ProfilesDirectory)\",'')}catch{$Sid}
#' Various info
$originalLocation
$deletedDate
$user
$sid
$infoFile.Fullname
((gi $infoFile -force).length / 1mb).ToString('0.00MB')
$originalFile.fullname
((gi $originalFile -force).length / 1mb).ToString('0.00MB')
""
# Blow it all Away
#ri $InfoFile -Recurse -Force -Confirm:$false -WhatIf
#ri $OriginalFile -Recurse -Force -Confirm:$false- WhatIf
# remove comment before two lines above and the '-WhatIf' statement to delete files
}
}
# Refresh desktop icons
[WinAPI.Explorer]::Refresh()
This works well also as a script with the task scheduler.
Clear-RecycleBin -Force