How to Pipe Multiple Commands to CSV File [Powershell] - powershell

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.

Related

Creating a Powershell script to get network folder information Problem with capturing and displaying LastWriteTime

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

PowerShell Test If a Filename From a List Exists Somewhere In a Directory and Export Missing

I've researched this and haven't been able to come up with a solid solution. Basically, I have a separate hard drive containing thousands of music files. I have a CSV list with the names of all the files that should be in the hard drive. Example:
My List
I want to be able to test if each of the files on my list exist in the hard drive, and if not, export it to a separate "missing files" list. The thing is each of the files in the hard drive exist under multiple folders.
As my script is now, I am trying to test if the path exists by using join-path. Here is my code right now - it's returning all of the files in the directory instead of just the missing files:
$documents = 'C:\Users\Me\Documents\ScriptTest'
$CSVexport = 'C:\Users\Me\Documents\ScriptTest\TestResults.csv'
$obj = #()
Write-host "`n_____Results____`n" #Write the results and export to a CSV file
$NewCsv = Import-CSV -Path 'C:\Users\Me\Documents\ScriptTest\Test.csv' |
Select-Object ID,'File Path' |
ForEach-Object {
if (!(Test-Path (Join-Path $documents $_.'File Path'))){
write-host "`n$($_.'File Path') is missing from the folder.`n"
$ObjectProperties = #{
ID = $_.ID
'File Path' = $_.'File Path'
}
$obj += New-Object PSObject -Property $ObjectProperties
}
}
$obj | export-csv $CSVexport -NoTypeInformation
How do I account for the sub-directories that vary with each file?
Edit - Resolved
$myFolder = 'C:\Users\Me\Documents\ScriptTest'
$CSVexport = 'C:\Users\Me\Documents\ScriptTest\Results.csv'
$csvPath = 'C:\Users\Me\Documents\ScriptTest\Test.csv'
$FileList = Get-ChildItem $myFolder -Recurse *.wav | Select-Object -ExpandProperty Name -Unique
Import-CSV -Path $csvPath |
Where-Object {$FileList -notcontains $_.'File Path'} |
export-csv $CSVexport -NoTypeInformation
You could generate a list of filenames from the recursed folders, then check if the file is in that list.
$documents = 'C:\Users\Me\Documents\ScriptTest'
$CSVexport = 'C:\Users\Me\Documents\ScriptTest\TestResults.csv'
$FileList = Get-ChildItem $documents -Recurse |
Where-Object { -not $_.PSIsContainer } |
Select-Object -ExpandProperty Name -Unique
Import-CSV -Path 'C:\Users\Me\Documents\ScriptTest\Test.csv' |
Where-Object {$FileList -notcontains $_.File} |
Export-CSV $CSVexport -NoTypeInformation
Edit: Answer updated to work with PowerShell 2.0 with suggestions from Bruce Payette and mklement0

Need help to locate the files along with size

I wrote a script in powershell to locate the files in share drives which takes the names of files from a .txt file. The script is as below.
$Files= Get-Content lock.txt
foreach ($File in $Files) {
get-childitem -path Z:\lonmgb003_2 -Name $File -Recurse
}
Could someone help me with identifying size of file along with path. Thanks in advance.
The -Name parameter is a switch - it doesn't take an argument - and it causes Get-ChildItem to output just the file name and nothing else. The $File argument is instead being bound to the -Filter parameter.
Remove the -Name parameter and then use the Select-Object cmdlet to select just the path and size.
Since you have all the file names in an array already, you might as well use the -Include parameter to retrieve them all at once:
Get-ChildItem -Path Z:\lonmgb003_2 -Include $Files -Recurse |Select FullName,Length
You would need the Length and FullName properties of the files. You can do something like this.
$array= #()
$Files= Get-Content lock.txt
foreach ($File in $Files)
{
get-childitem -path Z:\lonmgb003_2 -Recurse | ?{$_.Name -like $File}
$obj = New-Object PSObject
$Size = [Math]::Round((Get-ChildItem -Recurse $_.FullName | Measure-Object Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB, 2)
$obj | Add-Member -MemberType NoteProperty -Name "Path" $_.FullName
$obj | Add-Member -MemberType NoteProperty -Name "SizeinMB" $Size
$array +=$obj
}
$array | select Path, SizeinMB
What i have done is create a custom PS object that will store the FullName(precisely path) and Size of all the files. The use of -Name property doesn't help as it would just return you file name as a string and not the name as file (whose property you can use). Hence, a where-Object to match the file names in the text file. After your operation is done, the desired output is stored in an array named $array.

read multiple lines from text file and added it to registry

Very very new to PowerShell
First off all I'd like to give the scenario what I'm trying to do.
We have a SharePoint site and users can add their OneNote books to this SharePoint site. The URL's to the SharePoint site is now changing. So I'm exporting the registry entries to a text file, change the part of the path to the new one, and add the new URL's to the registry.
The problem I'm having is when I export the URL's (because of the way I'm exporting for sure) the text file has a string called "Value" and the URL's are under it.
So the first question is how do I write the values to a text file where only the values get written (in this case the URL's)
And second question is how do I write these changed values back to the registry?
Every URL is a new "string" and the names start with 1, 2, 3 and so on.
Thank you everyone for their time in advance.
# Create a new folder if not exist file
$Folder = "C:\backup"
if(-not(Test-Path $Folder)){
New-Item -Path $Folder -ItemType Directory
}
# Start Logging
Start-Transcript -Path "C:\backup\onenote.log"
#Set Variables
$OneNoteBooks = "C:\backup\onenotenotebooks.txt"
$NewPath = '//newpath.com/'
# Delete the existing file if exists
If (Test-Path $OneNoteBooks){Remove-Item $OneNoteBooks}
# Create a new text file
New-Item -Path $OneNoteBooks -ItemType File
# Exporting OneNote SharePoint Notebooks and Correcting them to the new URL
Write-Host "Exporting OneNote SharePoint Notebooks and Correcting them to the new URL"
Push-Location
Set-Location 'HKCU:\Software\Microsoft\Office\14.0\Onenote\opennotebooks'
Get-Item . | Select-Object -ExpandProperty Property | ForEach-Object {
New-Object PSobject -Property #{"property"=$_;
"Value" = (Get-ItemProperty -Path . -Name $_).$_}} | Format-Table Value -AutoSize | Out-File $OneNoteBooks
Pop-Location
$ReplaceURL = Get-Content -Path $OneNoteBooks
ForEach-Object{
$ReplaceURL -replace "`//.*?(`/)", $NewPath | Out-File $OneNoteBooks
}
# Add Changed URL's to the registry
ForEach-Object {
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion -Name PowerShellPath -PropertyType String -Value $PSHome
# Stop Logging
Stop-Transcript
Where to start? How about with the fact that in your ForEach loop you create an object with two properties, and then use Format-Table (alias FT used), and select one of the two properties making the whole object pointless... instead just get and output the value that you want.
Get-Item . | Select-Object -ExpandProperty Property | ForEach-Object {Get-ItemProperty -Path . -Name $_).$_} | Out-File $OneNoteBooks
Or better yet, use the built in functions of the Item from your Get-Item to get and set your values in a ForEach loop. Try this out...
# Create a new folder if not exist file
$Folder = "C:\backup"
if(-not(Test-Path $Folder)){
New-Item -Path $Folder -ItemType Directory
}
# Start Logging
Start-Transcript -Path "C:\backup\onenote.log"
#Set Variables
$OneNoteBooks = "C:\backup\onenotenotebooks.txt"
$NewPath = '//newpath.com/'
$OldPath = '//oldpath.com/'
$SitesChangingMask = "$([regex]::escape("$oldpath"))(nibremv|finance|sites/procurement|Comm|Departments/NITAS|ro|nitd|cnibr|communities/sig)/?"
# Delete the existing file if exists
If (Test-Path $OneNoteBooks){Remove-Item $OneNoteBooks}
# Create a new text file
New-Item -Path $OneNoteBooks -ItemType File
# Exporting OneNote SharePoint Notebooks and Correcting them to the new URL
"Exporting OneNote SharePoint Notebooks and Correcting them to the new URL"
Push-Location
Set-Location 'HKCU:\Software\Microsoft\Office\14.0\Onenote\opennotebooks'
$OneNoteReg = Get-Item .
$OneNoteReg.Property | ForEach-Object {
$CurrentValue = $OneNoteReg.GetValue($_)
$NewValue = if($CurrentValue.ToString() -match $SitesChangingMask){$CurrentValue.ToString() -replace "$OldPath", $NewPath}else{$CurrentValue.ToString()}
Set-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\Onenote\opennotebooks' -Name $_ -Value $NewValue
[PSCustomObject][Ordered]#{
"Value Name"=$_
"Original Value"=$CurrentValue
"Updated Value"=$NewValue
}
} | FT -AutoSize | Out-File $OneNoteBooks
Pop-Location
# Stop Logging
Stop-Transcript
I also changed your RegEx pattern for your replace since the old one didn't seem right. For example, in HTTP://www.microsoft.com/powershell it would update that string to read http://www.microsoft.com//newsite.com/powershell and I don't think that was your intention.
When saving data to a file, skip using the Format commands. You could use a CSV file here instead:
Get-Item . | Select-Object -Exp Property | ForEach-Object {
New-Object PSCustomObject -Property #{"Property"=$_;"Value"=(Get-ItemProperty . -Name $_).$_}} |
Export-Csv $OneNoteBooks
Modify the file, then re-read it like so:
$props = Import-Csv $OneNoteBooks
Then you can access the property names and values like so:
$props.Property[0]
$props.Value[0]
..
Of course, you don't really need to go to a file to modify the values. You could do it in memory:
$props = Get-Item . | Select-Object -Exp Property | ForEach-Object {
New-Object PSCustomObject -Property #{"Property"=$_;"Value"=(Get-ItemProperty . -Name $_).$_}}
$props = $props | Foreach {$_.Value = $_.Value -replace 'pattern','replacement'}
If you would prefer to create a .reg file that you can then import try this:
"Windows Registry Editor Version 5.00`n" > c:\onenotebooks.reg
"[$($pwd.ProviderPath)]" >> c:\onenotebooks.reg
Get-Item . | Select-Object -Exp Property |
ForEach-Object { "`"$_`"=`"$((Get-ItemProperty . -Name $_).$_)`"" } >> c:\onenotebooks.reg
The problem is in this line of code.
"Value" = (Get-ItemProperty -Path . -Name $_).$_}} | Format-Table Value -AutoSize | Out-File $OneNoteBooks
I'd suggest removing the format-table. If its not showing you everything then try this:
"Value" = (Get-ItemProperty -Path . -Name $_).$_}} | Foreach-Object {$_.Value} | Out-File $OneNoteBooks

File and folder count

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'
}