Calculate the hash of a file longer than 256 characters? - powershell

I am using Boe Prox's script to print a list of all the files and folders in a directory. I need Prox's script (as opposed to other windows print directory commands) because it uses robocopy to print filepaths longer than 260 characters.
My problem is, I also want the filehash to be printed alongside the file name. I have modified the script to obtain hashes (please see below) and it generally works except when the filepath is longer than 260 characters. Long filepaths get a blank in the hash column of the final output.
Research I have done:
According to this stackoverflow question, Robocopy has several switches that can be modified. I have scoured Boe's blog, as well as the list of robocopy commands, but there is nothing about a filehash switch.
Attempts to fix the problem:
I have also tried to modify the syntax of the filehash to make it more in line with the rest of the script ie. Hash = $matches.Hash
(this returns all blanks in place of the filehashs)
I tried taking off the part of the regex that seems to specify an item rather than the content of the item ie:If ($_.Trim() -match "^(?<Children>\d+)\s+") {
(this leads to the error code WARNING: Cannot bind argument to parameter 'Path' because it is null.)
I'm pretty hopeful that this can happen though: comments in Boe's original script includes the line: "Version 1.1 -Added ability to calculate file hashes"
Here's is my (partially working script):
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","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
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 #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
Length = [int64]$matches.Size
FileHash = (Get-FileHash -Path $matches.FullName).Hash
Created = (Get-Item $matches.FullName).creationtime
LastWriteTime = (Get-Item $matches.FullName).LastWriteTime
}
$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
}
}
}
}
Get-FolderItem "C:\TestingFileFolders"

In Windows PowerShell, you can prepend \\?\ to the full path and read the file:
Get-FileHash -Path "\\?\$($matches.FullName)"

Related

Powershell doesnt shows else condition when get error

when i tried to run the script
only first and second condition is triggered
when i tried for "e.g D:\random " where random folder is not exists, i got error message instead of triggering 3rd conditional "else"
function listChildFolder($folderPath)
{
#write your script here
$folderPath = Read-Host "input"
if ((Get-ChildItem $folderPath) -ne $null)
{ $folderPath| Get-ChildItem |Sort-Object -Property LastWriteTime -Descending |Format-Table name }
elseif ((Get-ChildItem $folderPath) -eq $null)
{ "Folder Empty" }
else {"Error: <Error message>"}
return
}
Since Get-ChildItem throws a terminating error when the folder path does not exist, the function will end there and the rest of the elseif or else conditions are never executed.
I would suggest doing this in a try{..} catch{..} so you can capture exceptions like that:
Something like
function listChildFolder {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$folderPath
)
# capture the terminating error message when the path does not exist
# by specifying -ErrorAction Stop
try {
# since we do not add switch '-File' or '-Directory',
# the Get-ChildItem cmdlet will return both types
$filesAndFolders = Get-ChildItem -Path $folderPath -ErrorAction Stop
# next, find out if we found any files or folders in the path
# the '#()' forces the $filesAndFolders variable into an array, so we can use the .Count property
if (#($filesAndFolders).Count) {
$filesAndFolders | Sort-Object LastWriteTime -Descending | Select-Object Name
}
else {
Write-Host "No files or subfolders found in '$folderPath'"
}
}
catch {
Write-Warning "Error: $($_.Exception.Message)"
}
}
$folderPath = Read-Host "Please enter a folder path"
# call the function
listChildFolder $folderPath
Another recommendation is that you use the PowerShell Verb-Noun naming convention for your function
As per your comment where you say you may not use try{..} catch{..}, there are other ways of course.
How about this:
function listChildFolder {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$folderPath
)
# test if the given folder path exists
if (Test-Path -Path $folderPath -PathType Container) {
# since we do not add switch '-File' or '-Directory',
# the Get-ChildItem cmdlet will return both types
$filesAndFolders = Get-ChildItem -Path $folderPath
# next, find out if we found any files or folders in the path
# the '#()' forces the $filesAndFolders variable into an array, so we can use the .Count property
if (#($filesAndFolders).Count) {
$filesAndFolders | Sort-Object LastWriteTime -Descending | Select-Object Name
}
else {
Write-Host "No files or subfolders found in '$folderPath'"
}
}
else {
Write-Warning "Error: '$folderPath' does not exist"
}
}
$folderPath = Read-Host "Please enter a folder path"
# call the function
listChildFolder $folderPath

Is there a way to include filepaths with diacritics in a robocopy script?

I have a script that extracts metadata from each file in a directory. When the filepath is free of diacritics, the script produces a csv file that looks like this:
When the filepath includes a diacritic (ie. "TéstMé.txt"), the csv file has blanks in the filehash field:
My question is: how do I get this script to work regardless of diacritics in the filepath?
I have determined that the problem is not with the Get-FileHash part of the script (When I run the single line Get-FileHash "C:\Temp\New\TéstMé.txt" a hash is produced.)
I have also determined that replacing FileHash = Get-FileHash -Path with FileHash = Get-FileHash -LiteralPath is not a solution, as it also produces a blank.
I tried to change the regex in the line ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)") { in case it was blocking diacritics, but any change would bring up WARNING: parsing [unique parsing error here].
I also tried to change ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True from $true to $false (in case the pipeline was changing the filepath value) but that had no effect.
I thought maybe Robocopy (which is used in the script) was incapable of handling files with diacritics, but Robocopy C:\Temp\New C:\Temp\star moves the files fine.
I do have a regex for identifying illegal characters (obtained from here) but I don't know how to incorporate it into the script.
FYI: I cannot change the actual file names. Would love to do a find-and-replace for any letter with a diacritic, but this option isn't open to me.
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","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
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 #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
}
$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
}
}
}
}
Get-FolderItem "C:\Temp\New" | Export-Csv -Path C:\Temp\testesting.csv
Here is a solution, I output the RoboCopy output to an unicode log using /UNILOG:c:\temp\test.txt params and then use the same code
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","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/UNILOG:c:\temp\test.txt"))
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 | Out-Null
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
}
$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
}
}
}
}
$a = Get-FolderItem "C:\Temp\New" | Export-Csv -Path C:\Temp\testtete.csv -Encoding Unicode

How to make Powershell Extract Metadata script run faster?

I have a script that I use to extract metadata from files in a network directory. It originates here, and I modified it in order to obtain additional metadata (size of file, filehash, date created,and lastwritetime), but these additions appear to slow down the script to the point that it takes weeks to complete when the number of files is more than 10000.
To illustrate the impact of the script additions on the speed, I ran the script on a folder containing five documents:
original script (no get-item or get-file hash lines): 2.9794699 seconds
with 'get-item' lines (size, filehash, created, lastwritetime): 7.6295035 seconds
with 'get-filehash' line : 6.9363834 seconds
with 'get-item' lines and 'get-filehash' lines: 12.4516334 seconds
I tried putting all the get-item lines together in a for-loop thinking that it would be faster to retrieve the file once from the network, then extract the metadata. While this modified script runs at a much faster 8.6488492 seconds, the metadata fields are not included in the output.
Here's the original script:
#Works on Powershell version 5.1
#The filepath of the folder being printed and the filepath where the output file will be placed need to be specified in the last line of script.
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","/E","/NJH","/NDL","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
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 #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
Stuff = foreach {$matches in $match}Length = (Get-Item $matches.FullName).length
FileHash = Get-FileHash -Path "\\?\$($matches.FullName)" |Select -Expand Hash
Created = (Get-Item $matches.FullName).creationtime
LastWriteTime = (Get-Item $matches.FullName).LastWriteTime
Owner = (Get-ACL $matches.Fullname).Owner
}
$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
}
}
}
}
Get-FolderItem "O:\directory\to\files" | Export-Csv -Path C:\output.csv
Does anyone know how to make the script run faster?
Rather than (Get-Item $matches.FullName).length you could do ([System.IO.FileInfo]$Matches.FullName).length. I see much better performance from that (Get-Item taking about 3.5x longer). Same for LastWriteTime.

Powershell Script to find the age of folder and presence

Created a PowerShell script to find the presence of Windows.old folder. Script will check for the age of the folder is more than 14 days, If the folder is present. Then give an output as '1' if the age is more than 14 days. The output should be '0' if the age is less than 14 days. Also the script should give an output as '0' if the folder is not present.
Below is the script which I created where the first two conditions are working fine. For the third scenario, it's giving the output (0) as required but showing an error as shown. Do we have an option to get the output without the error message.
$limit = (Get-Date).AddDays(-15)
$path = "C:\Windows.old"
$test = Test-Path $path
$task = ((Get-Item $path |
Where-Object { $_.CreationTime -lt $limit }))
if ($test -eq $true) {
if ($task) {
$compliance ='1'
} else {
$compliance ='0'
}
$compliance
}
if ($test -eq $false) {
Write-host '0'
}
So i wrote a function that should do what you need
function Get-ItemTest(){
Param(
[Parameter(ValueFromPipeline, Mandatory=$True)]
[string]$Path,
[Parameter(Mandatory=$True)]
[DateTime]$Limit
)
process{
if(Test-Path $Path){
Get-Item $path | %{
if($_.Creationtime -lt $Limit){
new-object psobject -Property #{Name=$_.FullName;Compliance=1}
}else{
new-object psobject -Property #{Name=$_.FullName;Compliance=0}
}
}
}else{
new-object psobject -Property #{Name=$Path;Compliance=0}
}
return $Response
}
}
Get-ItemTest -Limit (get-date).AddDays(-15) -Path "C:\Windows"
"C:\Windows.old", "C:\Windows","C:\Users" | Get-ItemTest -Limit (get-date).AddDays(-15)
Lets go over what is happening here.
In your script
$task = ((Get-Item $path | Where-Object { $_.CreationTime -lt $limit }))
you are already trying to get the Item before you test if the path is working.
You can instead encapsulate that in a IF statement with Test-Path
if(Test-Path $Path){
Get-Item ...
}else{
"failed"
}

Powershell Script is printing out duplicate entries of the same path

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