I am writing an application that looks through a directory tree and reports if a folder is inactive based on last write time and read only attribute.
However my loop stops after like 7 iterations even though there are thousands of folders.
My code looks like:
function FolderInactive{
Param([string]$Path)
$date = (Get-Date).AddDays(-365)
$anyReadOnly = $false
Get-ChildItem $Path -File -ErrorAction SilentlyContinue | ForEach-Object {
if($_.LastWriteTime -ge $date){
$false
continue
}
if($_.IsReadOnly -eq $false){
$anyReadOnly = $true
}
}
$anyReadOnly
}
Get-ChildItem "some drive" -Recurse | where {$_.PSIsContainer} | Foreach-Object {
Write-Host $_.FullName
FolderInactive($_.FullName)
}
If I comment out the FolderInactive function call in the Foreach loop it prints all the folders, but with the function call it stops after a few iterations. What is happening?
You cannot use continue with the Foreach-Object cmdlet. Foreach-Object is a cmdlet, not a loop. You instead want to use the loop:
function FolderInactive{
Param([string]$Path)
$date = (Get-Date).AddDays(-365)
$anyReadOnly = $false
$items = Get-ChildItem $Path -File -ErrorAction SilentlyContinue
foreach($item in $items)
{
if($item.LastWriteTime -ge $date){
$false
continue
}
if($item.IsReadOnly -eq $false){
$anyReadOnly = $true
}
}
$anyReadOnly
}
This also can be simplified:
function FolderInactive
{
Param([string]$Path)
$date = (Get-Date).AddYears(-1)
$null -ne (Get-ChildItem $Path -File -ErrorAction SilentlyContinue |
Where {$_.LastWriteTime -ge $date -and $_.IsReadOnly})
}
Related
I have few folders in which my script runs and triggers job for each folder. Imagine if I have 10 subfolders inside the main folder and I want to limit the number of jobs running to at a time to be 5. how can I do that? I will put my code below.
function ThrottleJob([int]$MaxJob) {
do {
$jobs = Get-Job -state "Running"
Start-Sleep -Seconds 1
} while ($jobs.Count -gt $MaxJob)
}
$folders = #()
$folders += (Get-Item $path).FullName
$folders += Get-ChildItem -Path $path -Recurse -Directory
foreach ($folder in $folders) {
if (Get-ChildItem -Path $folder) {
ThrottleJob -MaxJob 5
Start-Job -InitializationScript $allJobs -ScriptBlock { runJob -path $args[0]} -ArgumentList $folder | Out-Null
}
}
Could anyone please help me out?
Here you can an example for setting a threshold for Start-Job, I do believe a normal foreach loop will be faster than this.
using namespace System.Collections.Generic
using namespace System.Management.Automation
$maxJobs = 5
$path = "$HOME\Documents"
$folders = #(
(Get-Item $path).FullName
(Get-ChildItem -Path $path -Recurse -Directory).FullName
)
$jobs = [List[Job]]::new()
foreach($folder in $folders)
{
$job = Start-Job {
Get-ChildItem $using:folder -File | Select-Object DirectoryName, Name
}
$jobs.Add($job)
while(($jobs.State -eq 'Running').Count -ge $maxJobs)
{
Clear-Host
Get-Job -State Running | Format-Table -AutoSize
Start-Sleep -Seconds 1
}
}
$result = Receive-Job $jobs -Wait -AutoRemoveJob
$result | Format-Table
As you can see, the code is awfully complicated. This is how it would look using Start-ThreadJob, which will most likely be faster than a foreach loop and will consume less memory than Start-Job.
$maxJobs = 5
$path = "$HOME\Documents"
$folders = #(
(Get-Item $path).FullName
(Get-ChildItem -Path $path -Recurse -Directory).FullName
)
$jobs = foreach($folder in $folders)
{
Start-ThreadJob {
Get-ChildItem $using:folder -File | Select-Object DirectoryName, Name
} -ThrottleLimit $maxJobs
}
$result = Receive-Job $jobs -Wait -AutoRemoveJob
$result | Format-Table
I have a set of variables that I want to be re-evaluated everytime through the for-loop. Reasoning, is these variables will change (from true to false) for example. However, it doesn't seem that they are being re-evaluated, but only compiled the first time through. This is an example of some variables that I want to be compiled every iteration.
For($i = 0; $i -lt 4; $i++) {
$IsPathEmpty = (Get-ChildItem -Path $NewFolderPath | Measure-Object).Count -
eq 0
$OldFolderPathFiles = Get-ChildItem -Path $OldFolderPath
$NewFolderPathFiles = Get-ChildItem -Path $NewFolderPath
$AreFilesDifferent = Compare-Object -ReferenceObject $OldFolderPathFiles -
DifferenceObject $NewFolderPathFiles
$BCompare = Get-Process -Name "BCompare" -ErrorAction SilentlyContinue
#$noDiffs = '' -eq ((Get-Content -Raw C:\bcreport\report.txt) -split '\r?\n-
+\r?\n')[1].Trim()
$noBCReport = Test-Path -Path "C:\B\report.txt"
if ($noBCReport -eq $true) {
$noDiffs = '' -eq ((Get-Content -Raw C:\bcreport\report.txt) -split '\r?
\n-+\r?\n')[1].Trim()
}
if ($IsPathEmpty -eq $true -or $AreFilesDifferent -ne $null) {
Robocopy $OldFolderPath $NewFolderPath *.* /E /COPY:DATS /DCOPY:T /purge
/MT:32 /R:2 /W:5 /LOG:$RoboLog
#HAD TO REMOVE ROBOCOPY USERNAME INFORMATION
$SetPermissions
} elseif($noBCReport -eq $false -and $BCompareNumber -lt 1) {
# &"C:\Program Files\Beyond Compare 4\BCompare.exe" $OldFolderPath
$NewFolderPath
mkdir -Path C:\scripts
New-Item C:\scripts\script.txt
Set-Content C:\scripts\script.txt 'load "%1" "%2"
expand all
folder-report layout:side-by-side options:display-mismatches output-
to:"%3"'
&"C:\Program Files\Beyond Compare
4\BCompare.exe"`#"C:\scripts\script.txt" $OldFolderPath $NewFolderPath
"C:\bcreport\report.txt"
$noDiffs = '' -eq ((Get-Content -Raw C:\bcreport\report.txt -ErrorAction
SilentlyContinue) -split '\r?\n-+\r?\n')[1].Trim()
$noBCReport = Test-Path -Path "C:\BCReport\report.txt"
$BCompareNumber = 2
}
elseif($noDiffs -eq $true -and $DiffNumber -lt 1 ) {
Remove-SMBShareAndDeleteFolder -ShareName $ShareName
Add-NewSharePath -ShareName $ShareName
$DiffNumber = 2
} elseif($noDiffs -eq $false -and $NewDiffNumber -lt 1) {
&"C:\Program Files\Beyond Compare 4\BCompare.exe" $OldFolderPath $NewFolderPath
[System.Windows.MessageBox]::Show('There were differences in files. Check Beyond Compare for results.')
$NewDiffNumber = 2
}
}
}
Specifically, the noBCReport variable will change from false to true after the second pass through the for loop. At that point, $noDiffs will also no longer be null. Then, based on the results of $noDiffs the script will continue the for-loop. However, since the $noBCReport variable never gets re-evaluated, it always shows false and the $noDiffs is always null. Any ideas?
My objective is to write a powershell script that will recursively check a file server for any directories that are "x" (insert days) old or older.
I ran into a few issues initially, and I think I got most of it worked out. One of the issues I ran into was with the path limitation of 248 characters. I found a custom function that I am implementing in my code to bypass this limitation.
The end result is I would like to output the path and LastAccessTime of the folder and export the information into an easy to read csv file.
Currently everything is working properly, but for some reason I get some paths output several times (duplicates, triples, even 4 times). I just want it output once for each directory and subdirectory.
I'd appreciate any guidance I can get. Thanks in advance.
Here's my code
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function Get-FolderItem
{
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin
{
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NFL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge'])
{
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge'])
{
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process
{
ForEach ($item in $Path)
{
Try
{
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop))
{
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile'])
{
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
}
Else
{
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try
{
If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)")
{
$object = New-Object PSObject -Property #{
ParentFolder = $matches.fullname -replace '(.*\\).*','$1'
FullName = $matches.FullName
Name = $matches.fullname -replace '.*\\(.*)','$1'
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
}
Else
{
Write-Verbose ("Not matched: {0}" -f $_)
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Function ExportFolders
{
#================ Global Variables ================
#Path to folders
$Dir = "\\myFileServer\somedir\blah"
#Get all folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 800
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderPath = $SubDir.FullName
$folders = Get-ChildItem -Recurse $FolderPath -force -directory| Where-Object { ($_.LastAccessTimeUtc -le $cutOffDate)} | Select-Object FullName, LastAccessTime
ForEach ($folder in $folders)
{
$folderPath = $folder.fullname
$fixedFolderPaths = ($folderPath | Get-FolderItem).fullname
ForEach ($fixedFolderPath in $fixedFolderPaths)
{
#$fixedFolderPath
$getLastAccessTime = $(Get-Item $fixedFolderPath -force).lastaccesstime
#$getLastAccessTime
$details = #{ "Folder Path" = $fixedFolderPath; "LastAccessTime" = $getLastAccessTime}
$results += New-Object PSObject -Property $details
$results
}
}
}
}
ExportFolders
I updated my code a bit and simplified it. Here is the new code.
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function ExportFolders
{
#================ Global Variables ================
#Path to user profiles in Barrington
$Dir = "\\myFileServer\somedir\blah"
#Get all user folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 1
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
$details = $null
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderName = $SubDir.FullName
$FolderInfo = $(Get-Item $FolderName -force) | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$FolderLeafs = gci -Recurse $FolderName -force -directory | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0} | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$details = #{ "LastAccessTime" = $FolderInfo.LastAccessTime; "Folder Path" = $FolderInfo.FullName}
$results += New-Object PSObject -Property $details
ForEach ($FolderLeaf in $FolderLeafs.fullname)
{
$details = #{ "LastAccessTime" = $(Get-Item $FolderLeaf -force).LastAccessTime; "Folder Path" = $FolderLeaf}
$results += New-Object PSObject -Property $details
}
$results
}
}
ExportFolders
The FolderInfo variable is sometimes printing out multiple times, but the FolderLeaf variable is printing out once from what I can see. The problem is if I move or remove the results variable from usnder the details that print out the folderInfo, then the Parent directories don't get printed out. Only all the subdirs are shown. Also some directories are empty and don't get printed out, and I want all directories printed out including empty ones.
The updated code seems to print all directories fine, but as I mentioned I am still getting some duplicate $FolderInfo variables.
I think I have to put in a condition or something to check if it has already been processed, but I'm not sure which condition I would use to do that, so that it wouldn't print out multiple times.
In your ExportFolders you Get-ChildItem -Recurse and then loop over all of the subfolders calling Get-FolderItem. Then in Get-FolderItem you provide Robocopy with the /S flag in $params.AddRange(#("/L", "/S", "/NJH", "/BYTES", "/FP", "/NC", "/NFL", "/TS", "/XJ", "/R:0", "/W:0")) The /S flag meaning copy Subdirectories, but not empty ones. So you are recursing again. Likely you just need to remove the /S flag, so that you are doing all of your recursion in ExportFolders.
In response to the edit:
Your $results is inside of the loop. So you will have a n duplicates for the first $subdir then n-1 duplicates for the second and so forth.
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
$results
}
should be
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
}
$results
I'm trying to create a powershell script that searches a users C drive for a certain file extension, and then writes a file to a network share if it finds one. The script is launched from the last line of a logon script that reads like this:
powershell.exe -ExecutionPolicy Bypass -windowstyle hidden -file "\\servername\Path\To\File.ps1"
And my powershell script looks like this:
$hostname = HostName
Get-ChildItem -Path C:\*.* -Filter $file -Recurse -Force -ErrorAction
SilentlyContinue | Out-File \\servername\Path\To\Results\$hostname\file.txt
If ((Get-Content "\\servername\Path\To\Results\$hostname\file.txt") -eq $Null) {
Remove-Item \\servername\Path\To\Results\$hostname\file.txt
}
Exit
The script runs perfectly fine on my machine even when I load it from the network share but whenever another computer runs it, it never produces an Out File. And I don't think it is even searching.
What am I doing wrong? I thought it was execution policy, but I've set the logon script to bypass it. I don't understand why it isn't working.
[edit]
I've now got the script working sometimes on Windows 10 machines. But it doesn't work at all on Windows 7. I've even tried running
Get-ChildItem C:\*.pst -Recurse
directly from a powershell command prompt, and it just fails silently and doesn't search for anything. Isn't Get-ChildItem a powershell 1 command?
Hello. If you do like this: Get-ChildItem -Path C:*.* -Filter $file
-Recurse -Force the text file output will be enough to weigh.
You can try to check the access to the network folder for the current
user:if access explicitly set, and write access exists, then you can
record a file with the content. Otherwise, it can create folder test
on the local machine which will create the file, indicating that there
is no access. How is this way:
Set-ExecutionPolicy remotesigned -Scope CurrentUser -Force| Out-Null
$user = [System.Environment]::UserName
$hostname = [System.Environment]::MachineName
try {
$accs = Get-ACL -Path "\\server\sharedfolder\Results\"
foreach ($access in $accs) {
$obj = New-Object -TypeName PSObject -Property #{
Access = $accs.Access
}
$obj1 = $obj | select -ExpandProperty Access
for ($i = 0 ; $i -le $obj1.Count ; $i ++ )
{
if (!($obj1[$i].IdentityReference -like "*Users*" -or $obj1[$i].IdentityReference -like "*$user*")) {
if (!(Test-Path "c:\test")) {
md c:\test
$s = "user access from group"
$s | out-file C:\test\ErrInfo.csv
}
else {
$s = "user access from group"
$s | out-file C:\test\ErrInfo.csv
}
}
if ($obj1[$i].IdentityReference -like "*Users*" -or $obj1[$i].IdentityReference -like "*$user*") {
if ($obj1[$i].FileSystemRights -like "*ReadAndExecute*")
{
if (!(Test-Path "c:\test")) {
md c:\test
$s = "Premission only ReadAndExecute"
$s | out-file C:\test\ErrInfo_rex.csv
}
else {
$s = "Premission only ReadAndExecute"
$s | out-file C:\test\ErrInfo_rex.csv
}
}
if ($obj1[$i].FileSystemRights -like "*FullControl*" -and $obj1[$i].AccessControlType -like "*Allow*" -or $obj1[$i].FileSystemRights -like "*Modify*" -and $obj1[$i].AccessControlType -like "*Allow*")
{
if (!(Test-Path "\\server\sharedfolder\Results\$hostname"))
{
md "\\server\sharedfolder\Results\$hostname"
Get-ChildItem -Path C:\testpatch\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
else {
Get-ChildItem -Path C:\testpatch\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}
}
}
}
}
catch {
if (!(Test-Path "c:\test")) {
md c:\test
$s = "--NoAccess--"
$s | out-file C:\test\ErrInfo_noaccess.csv
}
else {
$s = "--NoAccess--"
$s | out-file C:\test\ErrInfo_noaccess.csv
}
}
Or you can do something like this (whiteout EXIT):
Set-ExecutionPolicy remotesigned -Scope CurrentUser -Force| Out-Null
$hostname = [System.Environment]::MachineName
if (!(Test-Path "\\server\sharedfolder\Results\$hostname")) {
md "\\server\sharedfolder\Results\$hostname" | Out-Null
If ((Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue).Count -ne "0") {
Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}
elseif (Test-Path "\\server\sharedfolder\Results\$hostname") {
If ((Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue).Count -ne "0") {
Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}
I'm trying to delete all files (not folders) in %TEMP% which are older than 30 days. The problem is that some files are in use by a program so they can not be deleted. I tried to solve the problem as follow:
function IsFileLocked($filePath){
#write-host $filePath
Rename-Item $filePath $filePath -ErrorVariable errs -ErrorAction SilentlyContinue
$errs.Count
if ($errs.Count -ne 0)
{
return $true #File is locked
}
else
{
return $false #File is not locked
}
}
$Path= "$env:temp"
if ((Test-Path -Path $Path) -ieq $true)
{
$Daysback = '-30'
$CurrentDate = Get-Date
$DatetoDelete = $CurrentDate.AddDays($Daysback)
get-childitem $Path -recurse | Where-Object {$_.LastWriteTime -lt $DatetoDelete } |
Where-Object {$_.PSIsContainer -eq $False }| Where-Object {(IsFileLocked -filePath "($_)") -eq $false }# | remove-item -force #-WhatIf
}
The problem is that (IsFileLocked -filePath "($_)") -eq $false doesn't return any element.
Is it possible that get-childitem blocks the files, so that all of them are locked when I run get-childitem?
Any other ideas how to solve this problem?
How about just removing files older than 30 days and ignore the errors:
$old = (Get-Date).AddDays(-30)
Get-ChildItem $env:TEMP -Recurse |
Where-Object {!$_.PSIsContainer -and $_.LastWriteTime -lt $old } |
Remove-Item -Force -ErrorAction SilentlyContinue