Alright, I am confusing my self on how to get this to work. I have several folder locations that have multiple sub-folders. These sub-folders are all named after hosts on our network. So I am working on a script to validate the contents of the folders for auditing purposes. I can not seem to generate a usable list that has the system name derived from the folder name and the full path name. I.G.
Name Path
---- ----
system1 \\path\rootfolder1\system1
system2 \\path\rootfolder1\system2
system3 \\path\rootfolder2\system3
I get the root folders from a CSV file as the folders are not all in one location and I do not need to use every folder time I run the report.
#Path to folder repository. Folder names must be the systems host name.
$list_paths = (Import-Csv 'C:\CVS\path\Paths.csv').path
#list arrays
$list = #()
$list2= #()
#Counters
$p_count = 0
$l_count = 0
#Generates array (list) of folder paths
Foreach ($p1 in $list_paths){
$l_count ++
$listx1 = Get-ChildItem $p1 | Where-Object {$_.PSIsContainer} | Foreach-object {$_.FullName}
$list += $listx1
}
#Generates array (list) of system names from folder
ForEach ($p2 in $list){
$p_count ++
Write-Host $p2
$listx2 = split-path -path $p2 -leaf
$list2 += $listx2
}
$Output = New-Object PSObject -Property #{
"Name" = $list
"Path" = $list2
}
Write-Host ($Output | Format-table | Out-String)
Write-Host Number of root folders
Write-Host $l_count
Write-Host Number of host folders
Write-Host $p_count'
So when I run the script $output produces this instead of the format I want above.
Name
----
{\\path\rootfolder1\system1, \\path\rootfolder2\system2, \\path\root...}
I know I am doing something wrong but I can seem to figure out what.
You only create one object with every name and every path as the property-values, not one "Name + Path"-object per system-folder like your first example. Also, you're mixing your lists, so the paths ended up in the Name-property.
Try moving the New-Object job inside a foreach-loop that processes the system-folders. I'd also recommend readable variable-names.
#Path to folder repository. Folder names must be the systems host name.
$rootpaths = (Import-Csv 'C:\CVS\path\Paths.csv').path
#Systems found
$systems = #()
#Find system-folders inside each root
Foreach ($root in $rootpaths){
Get-ChildItem $root | Where-Object { $_.PSIsContainer } | Foreach-object {
#Foreach system-folder, generate a result object
$systems += New-Object PSObject -Property #{
#No need to split the path. The object already contains the leaf-name in the Name-property
"Name" = $_.Name
"Path" = $_.FullName
}
}
}
#No need for write-host if you're writing everything as strings anyways
$systems | Format-table | Out-String
"Number of root folders: $($rootpaths.Count)"
"Number of host folders $($systems.Count)"
As mentioned by #TheMadTechnician, if use the pipeline for what it's worth, this can actually be shortened down to:
#Path to folder repository. Folder names must be the systems host name.
$rootpaths = (Import-Csv 'C:\CVS\path\Paths.csv').path
#Find system-folders inside each root
$systems = Get-ChildItem -Path $rootpaths | Where-Object { $_.PSIsContainer } | Select-Object Name, FullName
#Or this if you have Powershell 3.0+
#$systems = Get-ChildItem -Path $rootpaths -Directory | Select-Object Name, FullName
#No need for write-host if you're writing everything as strings anyways
$systems | Format-table | Out-String
"Number of root folders: $($rootpaths.Count)"
"Number of host folders $($systems.Count)"
Related
Okay i am not a programmer and my Powershell experience is basic. But here goes. I have been asked to collect some info on a Directory we are migrating off our network.
It collects sub dirs names, size, #of files and folders and datestamp and exports to csv.
I cannot for the life of me make the folder creation date work so i gave up on that and have been looking to get the lastwritetime for the folders as i am trying to figure out what has been used recently. It only works for a few folders but the rest in excel have system.object[] in the cell. Super frustrating.
Here is the code. It uses a gui directory picker.
#Refresh network drives for session
net use i: /delete
net use m: /delete
net use i: "\\wfs.queensu.ca\ADV\Workgroups"
net use m: "\\wfs.queensu.ca\ADVMedia"
Function Get-Folder($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null
$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.Description = "Select a folder"
$foldername.rootfolder = "MyComputer"
if($foldername.ShowDialog() -eq "OK")
{
$folder += $foldername.SelectedPath
}
return $folder
}
$forDir = Get-Folder
#Change this to the parent directory that you want counts for
#$forDir = "\\wfs.queensu.ca\adv\workgroups\ADV Services\$seldir"
$Dirs = Get-ChildItem $forDir -Directory -Name
$Tab = [char]9
$results = #()
Write-Host $forDir
foreach($Dir in $Dirs)
{
$dirSize = "{0:N2} MB" -f ((Get-ChildItem $forDir/$Dir -Recurse | Measure-Object -Property Length
-Sum -ErrorAction Stop).Sum / 1MB)
$dirFiles = Get-ChildItem $forDir/$Dir -Recurse -File | Measure-Object | %{$_.Count}
$dirFolders = Get-ChildItem $forDir/$Dir -Recurse -Directory | Measure-Object | %{$_.Count}
#$dirDate = (Get-ChildItem $forDir/$Dir).LastWriteTime.ToString
$dirDate = #(Get-ChildItem $forDir/$Dir | % {$_.LastWriteTime})
$details = [ordered] #{
dir = $Dir
No_Files = $dirFiles
No_Folders = $dirFolders
size = $dirSize
date = $dirDate
}
$results += New-Object PSobject -Property $details
}
#This line finds the last index of the slash and adding one char
$Dirlength = $forDir.LastIndexOf('\') + 1
#This line takes the entire length of the string minus the postion above leaving the directory name
$sublength = $forDir.length - $Dirlength
#Assigns the remaining characters from the substring to the varibale to be used as the filename
$DirName = $forDir.SubString($Dirlength, $sublength)
$results | Export-Csv "C:\$DirName.csv" -NoTypeInformation
Write-Host ("Complete WOW!")
Get-ChildItem .\dir gives you all files contained in the directory .\dir not the directory itself.
That is why the following line in your script creates an array of LastWriteTimes for all files that are contained in the directory that $forDir/$Dir resolves to in your foreach loop:
$dirDate = #(Get-ChildItem $forDir/$Dir | % {$_.LastWriteTime})
The array in $dirDate will return ​System.Object[] when its toString() method is called. This is the reason, why you see this string in your excel, where you expect the folder's timestamp.
I bet that those folders, that seem to work do have exactly one childitem...
To get the LastWriteTime of the directory itself use Get-Item instead of Get-ChildItem.
$dirDate = Get-Item $forDir/$Dir | Select-Object -Expand LastWriteTime
try this...
Get-ChildItem -Path 'D:\temp' -Recurse |
Where-Object { $_.PSIsContainer } |
Select-Object -Property Name, LastWriteTime
<#
# Results
Name LastWriteTime
---- -------------
est 17-Feb-20 15:50:53
LogFiles 11-Mar-20 11:37:28
NewFolder 06-Feb-20 14:56:48
ParentFolder 12-Feb-20 14:24:25
Reference 03-Feb-20 11:55:47
Source 06-Feb-20 14:56:48
Target 24-Feb-20 22:03:56
New folder 03-Feb-20 11:55:24
temp 20-Jan-20 11:17:42
ChildFolder 12-Feb-20 14:08:11
GrandchildFolder 12-Feb-20 14:08:32
#>
# Or in v3 and beyond
Get-ChildItem -Path 'D:\temp' -Directory -Recurse |
Select-Object -Property Name, LastWriteTime
<#
# Results
Name LastWriteTime
---- -------------
est 17-Feb-20 15:50:53
LogFiles 11-Mar-20 11:37:28
NewFolder 06-Feb-20 14:56:48
ParentFolder 12-Feb-20 14:24:25
Reference 03-Feb-20 11:55:47
Source 06-Feb-20 14:56:48
Target 24-Feb-20 22:03:56
New folder 03-Feb-20 11:55:24
temp 20-Jan-20 11:17:42
ChildFolder 12-Feb-20 14:08:11
GrandchildFolder 12-Feb-20 14:08:32
#>
I know this question has already been answered, but for completeness, here's another way of doing this by utilizing the GetFileSystemInfos method every DirInfo object has.
$rootFolder = 'X:\YourRootPath'
Get-ChildItem -Path $rootFolder -Directory -Recurse | ForEach-Object {
# GetFileSystemInfos() (needs .NET 4+) is faster than Get-ChildItem and returns hidden objects by default
# See: https://devblogs.microsoft.com/powershell/why-is-get-childitem-so-slow/
$fsObjects = $_.GetFileSystemInfos('*', 'TopDirectoryOnly') # TopDirectoryOnly --> do not recurse
# you can also use Get-ChildItem here of course.
# To also get hidden files, with Get-ChildItem you need to add the -Force switch
# $fsObjects = Get-ChildItem -Path $_.FullName -Filter '*' -Force
# from the $fsObjects array, filter out the files and directories in order to get the count
$files = $fsObjects | Where-Object { $_ -is [System.IO.FileInfo] } # or: !($_.Attributes -band 'Directory')
$folders = $fsObjects | Where-Object { $_ -is [System.IO.DirectoryInfo] } # or: $_.Attributes -band 'Directory'
# emit a PSObject with all properties you wish to collect
[PsCustomObject]#{
Path = $_.FullName
FileCount = $files.Count
DirCount = $folders.Count
DirSize = "{0:N2} MB" -f (($files | Measure-Object -Sum -Property Length).Sum / 1MB)
DirDate = $_.LastWriteTime
}
} | Export-Csv -Path "X:\YourFolder_Info.csv" -NoTypeInformation -UseCulture
I have a working PS script that's used to compare the contents of two directories and report any missing files or files that have different contents. This is what it currently does:
Takes two specific directories.
Pulls all of the files from each directory (minus any excluded paths/files).
Checks to see if any files are missing from one or the other.
For each file that is in both directories, it does a hash comparison of that file's contents.
Places the results in variables based on files in the source but not the destination and vice-versa, and files that were in both the source and destination but had different contents.
Below is the main chunk of code that does all this. What I need to change/add is the ability to list ONLY the specific paths I want to be compared between the two servers. For example, say I want the following paths to be used for the comparison:
D:\Files\Stuff
D:\Files\Contents\Folders\MoreStuff
D:\Files\Archive
I would want each of those directories compared between their counterpart on the other server. Meaning it would compare the D:\Files\Stuff path between server 1 and server 2. It would NOT compare the paths with each other, though. Meaning, I DON'T want it to compare D:\Files\Stuff with D:\Files\Archive, regardless of the server.
What's the best way I can achieve this?
$SourceDir = "\\12345-serverP1\D$\Files";
$DestDir = "\\54321-serverP2\D$\Files";
#The excluded array holds all of the specific paths, files, and file types you don't want to be compared.
$ExcludedPaths = #(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedPaths;
$ExcludedFiles = #(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedFiles;
#Script block which stores the filter for the Where-Object used to exclude chosen paths.
#This script is called with the -FilterScript parameter below.
$Filter = {
$FullName = $_.FullName
-not($ExcludedPaths | Where-Object {$FullName -like "$_*";})
}
#Grabs all files from each directory, minus the excluded paths and files, and assigns them to variables based on Source and Destination.
try {$SourceFiles = Get-ChildItem -Recurse -Path $SourceDir -Exclude $ExcludedFiles -Force -ErrorAction Stop | Where-Object -FilterScript $Filter;}
catch {Write-Output "$(Get-Date) The following Source path was not found: $SourceDir" | Out-File $ErrorLog -Append;}
try {$DestFiles = Get-ChildItem -Recurse -Path $DestDir -Exclude $ExcludedFiles -Force -ErrorAction Stop | Where-Object -FilterScript $Filter;}
catch {Write-Output "$(Get-Date) The following Destination path was not found: $DestDir" | Out-File $ErrorLog -Append;}
#Pulls the name of each file and assigns it to a variable.
$SourceFileNames = $SourceFiles | % { $_.Name };
$DestFileNames = $DestFiles | % { $_.Name };
#Empty variables to be used in the loops below.
$MissingFromDestination = #();
$MissingFromSource = #();
$DifferentFiles = #();
$IdenticalFiles = #();
#Iterates through each file in the Source directory and compares the name against each file in the Destination directory.
#If the file is missing from the Destination, it is added to the MissingFromDestination variable.
#If the file appears in both directories, it compares the hash of both files.
#If the hash is the same, it adds it to the IdenticalFiles variable. If the hash is different, it adds the Source file to the DifferentFiles variable.
try {
foreach ($f in $SourceFiles) {
if (!$DestFileNames.Contains($f.Name)) {$MissingFromDestination += $f;}
elseif ($DestFileNames.Contains($f.Name)) {$IdenticalFiles += $f;}
else {
$t = $DestFiles | Where { $_.Name -eq $f.Name };
if ((Get-FileHash $f.FullName).hash -ne (Get-FileHash $t.FullName).hash) {$DifferentFiles += $f;}
}
}
}
catch {Write-Output "$(Get-Date) The Destination variable is null due to an incorrect path." | Out-File $ErrorLog -Append;}
#Iterates through each file in the Destination directory and compares the name against each file in the Source directory.
#If the file is missing from the Source, it is added to the MissingFromSource variable.
try {
foreach ($f in $DestFiles) {
if (!$SourceFileNames.Contains($f.Name)) {$MissingFromSource += $f;}
}
}
catch {Write-Output "$(Get-Date) The Source variable is null due to an incorrect path." | Out-File $ErrorLog -Append;}
Is it possible to use $SecretFolder from the else statement in future Iterations if the company is the same. E.g. Multiple users exist on the list from one company but they all need to have a link generated for 1 folder for the company to access.
#Location of original dataset
$csv = Import-Csv c:\export.csv
#loops through every line of the csv
Foreach ($line in $csv){
#Generate random folder name (8 Characters long)
$SecretFolder = -join ((48..57) + (97..122) | Get-Random -Count 8 | % {[char]$_})
#Create URL
$url = "www.website.com.au/2017Rates/$SecretFolder"
#Test: Has the company already had a folder created
if (Get-Variable $line.CompanyName -Scope Global -ErrorAction SilentlyContinue)
{
#Append URL to CSV for a person who already has a company folder
$report =#()
$report += New-Object psobject -Property #{CompanyName=$line.CompanyName;FirstName=$line.FirstName;LastName=$line.LastName;EmailAddress=$line.EmailAddress;'Letter Type'=$line.'Letter Type';URL=$URL}
$report | export-csv testreporting.csv -Append
}
else
{
#Create Folder with Random Cryptic name
mkdir C:\Users\bford\test\$SecretFolder
#Copy item from FileLocation in CSV to SecretFolder Location
Copy-Item -Path $line.FileLocation -Destination c:\users\bford\test\$SecretFolder -Recurse -ErrorAction SilentlyContinue
#Create Variable for Logic test with the Name CompanyName
New-Variable -Name $line.CompanyName
#Append csv with the updated details
$S_report =#()
$S_report += New-Object psobject -Property #{CompanyName=$line.CompanyName;FirstName=$line.FirstName;LastName=$line.LastName;EmailAddress=$line.EmailAddress;'Letter Type'=$line.'Letter Type';URL=$url}
$S_report | export-csv testreporting.csv -Append
}
}
#Cleanup remove all the variables added
Remove-Variable * -ErrorAction SilentlyContinue
Do you have any reason to think it's impossible? Yeah it's possible, you should Google hashtables and find that they do everything you're trying to do with get-variable, only way better.
But your question amounts to "how do I rewrite my script so it works?" and rewriting your script to me means getting rid of the duplicate #()+= triple lines, the mystery numbers, the global variables, and the extra variables and the if/else, and it ends up a completely different script altogether.
A completely different, and mostly untested, script:
# import and group all people in the same company together
# then loop over the groups (companies)
Import-Csv -Path c:\export.csv |
Group-Object -Property CompanyName |
ForEach-Object {
# This loop is once per company, make one secret folder for this company.
$SecretFolder = -join ( [char[]]'abcdefghijklmnopqrstuvwxyz1234567890' | Get-Random -Count 8 )
New-Item -ItemType Directory -Path "C:\Users\bford\test\$SecretFolder"
# Loop through all the people in this company, and copy their files into this company's secret folder
$_.Group | ForEach-Object {
Copy-Item -Path $_.FileLocation -Destination c:\users\bford\test\$SecretFolder -Recurse -ErrorAction SilentlyContinue
}
# Output each person in this company with just the properties needed, and a new one for this company's URL
$_.Group | Select-Object -Property CompanyName , FirstName,
LastName, EmailAddress, 'Letter Type',
#{Name='Url'; Expression={"www.website.com.au/2017Rates/$SecretFolder"}}
} | Export-Csv -Path testreporting.csv -NoTypeInformation
But to edit your script to do what you want, use a hashtable, e.g.
$SecretFolders = #{} #at top of your script, outside loops
# in loops:
if (-not $SecretFolders.ContainsKey($line.CompanyName))
{
$SecretFolders[$line.CompanyName] = -join (random name generation here)
}
$SecretFolder = $SecretFolders[$line.CompanyName]
There is a folder on the remote server which has various subfolders in it. It is completely nested. I would like to:
Prepare an HTML report which contains folder name.
For every folder it should also record the file count.
The code needs to append the HTML file which is already created.
Columns required: Folder name, Folder Path, File Count
Below is the code snippet which is part of my main script. I am fairly new to PowerShell.
Can some one please help?
$server_dir = "D:\Data\Inbox"
$does_dir_e = (Test-Path $server_dir)
if($does_dir_e)
{
$fso = New-Object -com "Scripting.FileSystemObject"
$f = $fso.GetFolder($server_dir)
foreach($folder in $f.subfolders)
{
$fcount = $((Get-ChildItem $folder.Path).count)
$fname = $folder.name | Convertto-HTML -Fragment >> C:\Temp\Server.html
}
}
You don't actually say what isn't working for you, but the following script should get you started.
The outer loop recurses through the folders (PSIsContainer) means it is a folder.
The inner loop counts the number of files in each folder using measure-object, we filter out folders from this count to give us just the file count.
$path = "D:\Data\Inbox"
# Enumerate the given path recursively
Get-ChildItem -Path $path -Recurse | Where-Object {$_.PSIsContainer} | %{
# Add a user-defined custom member with a value of the filecount this
# time not recursively (using measure object)
$_ | add-member -membertype noteproperty -name FileCount -value (Get-ChildItem -Path $_.Fullname |
Where-Object {!$_.PSIsContainer} |
Measure-Object).Count
# Output the required values
$_ | select Name, FullName, FileCount | ConvertTo-Html -Fragment
}
Is this what you want? I haven't used the HTML cmdlet before, so be aware it's ugly : )
$server_dir = 'D:\Data\Inbox'
if(Test-Path $server_dir)
{
$folders = Get-ChildItem $server_dir -Recurse | where {$_.PSIsContainer}
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | where {!$_.PSIsContainer} | Measure-Object | Select-Object -Expand Count
$obj = New-Object psobject -Property #{FolderName = $fname; FolderPath = $fpath; FileCount = $fcount}
$output += $obj
}
#Output to HTML
$output | ConvertTo-Html -Fragment >> 'C:\Temp\Server.html'
}
I'm trying to get The Folder Info and Security Info for all the folders on our server.
But I'm not to familiar with Powershell here. Mind helping a newbie?
How to do I get the Security acl piped into the Text file?
Along with just the member objects of Folder Name, Size, sub folder count?
# Step 1 Get Folder Path
function Select-Folder($message='Select a folder', $path = 0) {
$object = New-Object -comObject Shell.Application
$folder = $object.BrowseForFolder(0, $message, 0, $path)
if ($folder -ne $null) {
$folder.self.Path
}
}
#Step 2:Search For Directories
$dirToAudit = Get-ChildItem -Path (Select-Folder 'Select some folder!') -recurse | Where {$_.psIsContainer -eq $true}
foreach ($dir in $dirToAudit)
{
#Step 3: Output: [Folder Path, Name, Security Owner, Size, Folder Count]
#Pipe To CSV Text File
Get-Acl -Path $dir.FullName | Select-Object PSPath, Path,Owner | export-csv C:\temp\SecurityData.csv
#I also want the Folder path, Size and SubFolder Count
}
#Step 4: Open in Excel
invoke-item -path C:\temp\SecurityData.csv
Here's some sites that I found useful on the subject: http://blogs.msdn.com/b/powershell/archive/2007/03/07/why-can-t-i-pipe-format-table-to-export-csv-and-get-something-useful.aspx
http://www.maxtblog.com/2010/09/to-use-psobject-add-member-or-not/
This task isn't particularly easy. First you will want to create a custom object that contains the properties you want. These properties will be added via different commands e.g.:
$objs = Get-ChildItem . -r |
Where {$_.PSIsContainer} |
Foreach {new-object psobject -prop #{Path=$_.FullName;Name=$_.Name;FolderCount=$_.GetDirectories().Length}}
$objs = $objs | Foreach { Add-Member NoteProperty Owner ((Get-Acl $_.Path).Owner) -Inp $_ -PassThru}
$objs | Export-Csv C:\temp\data.csv
Getting the folder size will take some extra work to compute.