I've written a PowerShell function to create a custom object and stores it into a hashtable. The issue I'm facing is retrieving that object. I need to retrieve that object because it contains an array, I need to loop through that array and write it to a text file.
function removeItem {
<#Madatory Parameters for function, it takes the path to the files/folders
to clean up and path to the hashtable.#>
Param([Parameter(Mandatory=$True)]
[string]$path,
[string]$writetoText,
[hashtable] $hashWrite=#{}
)
<#Begin if statement: Test if Path Exists#>
if (Test-Path ($path)) {
<#Begin if statement: Check if file is Directory#>
if ((Get-Item $path) -is [System.IO.DirectoryInfo]) {
$pathObj = [pscustomobject]#{
pathName = $path
Wipe = (Get-ChildItem -Path $path -Recurse)
Count = (Get-ChildItem -Path $path -Recurse | Measure-Object).Count
}
# Write-Output $pathObj.Wipe
#Add Data to Hashtable
$hashWrite.Add($pathObj.pathName,$pathObj)
foreach ($h in $hashWrite.GetEnumerator()) {
Write-Host "$($h.Name): $($h.Value)"
}
<#
[string[]]$view = $pathObj.Wipe
for ($i=0; $i -le $view.Count; $i++){
Write-Output $view[$i]
}
#>
$pathObj.pathName = $pathObj.pathName + "*"
}<#End if statement:Check if file is Directory #>
}
}
My function takes 3 arguments, a path, the text file path, and a hashtable. Now, I create a custom object and store the path, files/folders contained in that path, and the count. Now, my issue is, I want to retrieve that custom object from my hashtable so that I can loop though the Wipe variable, because it's an array, and write it to the text file. If I print the hashtable to the screen it see the Wipe variable as System.Object[].
How do I retrieve my custom object from the hash table so I can loop through the Wipe Variable?
Possible Solution:
$pathObj = [pscustomobject]#{
pathName = $path
Wipe = (Get-ChildItem -Path $path -Recurse)
Count = (Get-ChildItem -Path $path -Recurse | Measure-Object).Count
}
#Add Data to Hashtable
$hashWrite.Add($pathObj.pathName,$pathObj)
foreach ($h in $hashWrite.GetEnumerator()) {
$read= $h.Value
[string[]]$view = $read.Wipe
for ($i=0; $i -le $view.Count; $i++) {
Write-Output $view[$i]
}
}
Is this the ideal way of doing it?
There are uses for GetEnumerator(), but in your case you're better off just looping over the keys of the hashtable:
$hashWrite.Keys | % {
$hashWrite[$_].Wipe
} | select -Expand FullName
Related
How to alter the script so that it checks for the file hash only when the condition of File name matches with each folder {Pre,Post}
$result = [System.Collections.Generic.List[object]]::new()
$sb = {
process {
if($_.Name -eq 'Thumbs.db') { return }
[PSCustomObject]#{
h = (Get-FileHash $_.FullName -Algorithm SHA1).Hash
n = $_.Name
s = $_.Length
fn = $_.fullname
}
}
}
$refFiles = Get-ChildItem 'C:\Users\HP\hello\pre' -Recurse -File | & $sb
$diffFiles = Get-ChildItem 'C:\Users\HP\hello\post' -Recurse -File | & $sb
foreach($file in $diffFiles) {
# this file exists on both folders, skip it
if($file.h -in $refFiles.h) { continue }
# this file exists on reference folder but has changed
if($file.n -in $refFiles.n) {
$file.PSObject.Properties.Add(
[psnoteproperty]::new('Status', 'Changed in Ref')
)
$result.Add($file)
continue
}
# this file does not exist on reference folder
# based on previous conditions
$file.PSObject.Properties.Add(
[psnoteproperty]::new('Status', 'Unique in Diff')
)
$result.Add($file)
}
foreach($file in $refFiles) {
# this file is unique in reference folder, rest of the files
# not meeting this condition can be ignored since we're
# interested only in files on reference folder that are unique
if($file.h -notin $diffFiles.h) {
$file.PSObject.Properties.Add(
[psnoteproperty]::new('Status', 'Unique in Ref')
)
$result.Add($file)
}
}
$result | Format-Table
This Code Produces output for every hash differ from the Reference folder regardless of File Name. Thank you
I created a script that lists all the folders, subfolders and files and export them to csv:
$path = "C:\tools"
Get-ChildItem $path -Recurse |select fullname | export-csv -Path "C:\temp\output.csv" -NoTypeInformation
But I would like that each folder, subfolder and file in pfad is written into separate column in csv.
Something like this:
c:\tools\test\1.jpg
Column1
Column2
Column3
tools
test
1.jpg
I will be grateful for any help.
Thank you.
You can split the Fullname property using the Split() method. The tricky part is that you need to know the maximum path depth in advance, as the CSV format requires that all rows have the same number of columns (even if some columns are empty).
# Process directory $path recursively
$allItems = Get-ChildItem $path -Recurse | ForEach-Object {
# Split on directory separator (typically '\' for Windows and '/' for Unix-like OS)
$FullNameSplit = $_.FullName.Split( [IO.Path]::DirectorySeparatorChar )
# Create an object that contains the splitted path and the path depth.
# This is implicit output that PowerShell captures and adds to $allItems.
[PSCustomObject] #{
FullNameSplit = $FullNameSplit
PathDepth = $FullNameSplit.Count
}
}
# Determine highest column index from maximum depth of all paths.
# Minus one, because we'll skip root path component.
$maxColumnIndex = ( $allItems | Measure-Object -Maximum PathDepth ).Maximum - 1
$allRows = foreach( $item in $allItems ) {
# Create an ordered hashtable
$row = [ordered]#{}
# Add all path components to hashtable. Make sure all rows have same number of columns.
foreach( $i in 1..$maxColumnIndex ) {
$row[ "Column$i" ] = if( $i -lt $item.FullNameSplit.Count ) { $item.FullNameSplit[ $i ] } else { $null }
}
# Convert hashtable to object suitable for output to CSV.
# This is implicit output that PowerShell captures and adds to $allRows.
[PSCustomObject] $row
}
# Finally output to CSV file
$allRows | Export-Csv -Path "C:\temp\output.csv" -NoTypeInformation
Notes:
The syntax Select-Object #{ Name= ..., Expression = ... } creates a calculated property.
$allRows = foreach captures and assigns all output of the foreach loop to variable $allRows, which will be an array if the loop outputs more than one object. This works with most other control statements as well, e. g. if and switch.
Within the loop I could have created a [PSCustomObject] directly (and used Add-Member to add properties to it) instead of first creating a hashtable and then converting to [PSCustomObject]. The choosen way should be faster as no additional overhead for calling cmdlets is required.
While a file with rows containing a variable number of items is not actually a CSV file, you can roll your own and Microsoft Excel can read it.
=== Get-DirCsv.ps1
Get-Childitem -File |
ForEach-Object {
$NameParts = $_.FullName -split '\\'
$QuotedParts = [System.Collections.ArrayList]::new()
foreach ($NamePart in $NameParts) {
$QuotedParts.Add('"' + $NamePart + '"') | Out-Null
}
Write-Output $($QuotedParts -join ',')
}
Use this to capture the output to a file with:
.\Get-DirCsv.ps1 | Out-File -FilePath '.\dir.csv' -Encoding ascii
i write a script with a function.
here is the script with the function:
function GenerateHashesForProjects(){
[array]$result=#()
Write-Output "Genrate Hash Values"
$dependencyFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\..\Sources\_Dependencies"
#get all folder in a list below the dependency folder expect the "Modules" folder
$dependencyContent = Get-ChildItem -Path $dependencyFolder | where {$_.PSIsContainer -and ($_.Name -notlike "*Modules*")}
#Fill the result array with the project file name and the depending hash value of this file
foreach ($item in $dependencyContent) {
$denpencyProjects = Get-ChildItem -Path $item.Fullname | where { ($_ -like "*.csproj") }
$hashValue = (Get-FileHash $denpencyProjects.FullName -Algorithm MD5).Hash
$name = $denpencyProjects.Name
Write-Output "name: $name `nvalue: $hashValue"
$result += #($denpencyProjects.Name, $hashValue)
}
return $result
}
That script works fine.
Now i want to use this function also in another script. So i import the script and define a variable with that function. Here is the issue if a call the function without the variable it works fine but with the variable definition not, why?
Here is the second script with the import:
. Join-Path -Path $PSScriptroot -ChildPath "..\..\Build\Tools\GenerateHashesForProjects.ps1"
[array]$dependencyFileValues = GenerateHashesForProjects
This test works fine:
. Join-Path -Path $PSScriptroot -ChildPath "..\..\Build\Tools\GenerateHashesForProjects.ps1"
GenerateHashesForProjects
since you didn't post any responses to questions [grin], here is one way to rewrite your code.
what it does ...
creates an advanced function
uses the recommended name format for such
does not supply the "otta be there" Comment Based Help [grin]
defines the parameters
only the $Path is required.
defines but does not use a begin {} block
defines a process {} block
grabs a list of the dirs that branch from the source path
filters out the dirs that are in the $ExcludeDirList
gets the files in those dirs that match the $FileFilter
iterates thru that list
builds a [PSCustomObject] for each file with the desired details
you can add or remove them as needed.
sends that PSCO out to the calling code
the line that calls the function stores the entire set of results into the $Result variable and then shows that on screen.
a few notes ...
i had to change a lot of your details since i have no csproj files
there are no "what is happening" lines
if you need that, you can easily add such. i would NOT use Write-Output, tho, since that will pollute your output data.
there is no error detection OR error handling
here's the code ...
function Get-ProjectFileHash
{
<#
CommentBasedHelp goes here
#>
[CmdletBinding ()]
Param
(
[Parameter (
Mandatory,
Position = 0
)]
[string]
$Path,
[Parameter (
Position = 1
)]
[ValidateSet (
'MD5',
'MACTripleDES',
'RIPEMD160',
'SHA1',
'SHA256',
'SHA384',
'SHA512'
)]
[string]
$Algorithm = 'MD5',
[Parameter (
Position = 2
)]
[string[]]
$ExcludeDirList,
[Parameter (
Position = 3
)]
[string]
$FileFilter
)
begin {}
process
{
$ProjDirList = Get-ChildItem -LiteralPath $Path -Directory |
Where-Object {
# the "-Exclude" parameter of G-CI is wildly unreliable
# this avoids that problem [*grin*]
# build a regex OR listing to exclude
$_.Name -notmatch ($ExcludeDirList -join '|')
}
$FileList = Get-ChildItem -LiteralPath $ProjDirList.FullName -File -Filter $FileFilter
foreach ($FL_Item in $FileList)
{
[PSCustomObject]#{
FileName = $FL_Item.Name
DirName = $FL_Item.Directory
Algorithm = $Algorithm
Hash = (Get-FileHash -LiteralPath $FL_Item.FullName -Algorithm $Algorithm).Hash
}
}
}
end {}
} # end >>> function Get-ProjectFileHash
$Source = 'C:\ProgramData\chocolatey\lib'
$NotWanted = 'choco', '7zip', 'kb', 'bad', 'bkp'
$Filter = '*.nupkg'
$Result = Get-ProjectFileHash -Path $Source -Algorithm MD5 -ExcludeDirList $NotWanted -FileFilter $Wanted
$Result
truncated output ...
FileName DirName Algorithm Hash
-------- ------- --------- ----
autohotkey.nupkg C:\ProgramData\chocolatey\lib\autohotkey MD5 35A1B894AEA7D3473F3BBCBF5788D2D6
autohotkey.install.nupkg C:\ProgramData\chocolatey\lib\autohotkey.install MD5 EFE8AD812CBF647CFA116513AAD4CC15
autohotkey.portable.nupkg C:\ProgramData\chocolatey\lib\autohotkey.portable MD5 D31FA1B5496AAE266E4B0545835E9B19
[*...snip...*]
vcredist2015.nupkg C:\ProgramData\chocolatey\lib\vcredist2015 MD5 56321731BC0AEFCA3EE5E547A7A25D5E
vlc.nupkg C:\ProgramData\chocolatey\lib\vlc MD5 8177E24675461BDFF33639BF1D89784B
wiztree.nupkg
I have a file with length of 309247 bytes as this image:
If I run this powershell script:
(Get-Item 'C:\TestFile.082').length
309247
But if run the same script but a $file variable it returns this:
$fpath = Get-Item $file.FullName
$fpath.length
309800
# or
(Get-Item $file.FullName).length
309800
# or
$file.length
309800
NOTE $file is a variable into a loop for all files contained in a directory
Why if I'm referencing the same file the results are different?
How can I do to get the real value (309247)?
This is my Full Code
$filesToFtp = Get-Item $pathToFtp -Exclude "*.PreARM"
foreach($file in $filesToFtp)
{
$fpath = Get-Item $file.FullName
$fpath.Length
$FileNode = $oXMLDocumentFiles.CreateElement("element","curtmpremotelxml_files","")
$nodoXML = "<versionnum>"+$versplain+"</versionnum>"+
"<filename>"+$file.Name.ToUpper()+"</filename>"+
"<filesize>"+$file.length+".00</filesize>"+
$FileNode.InnerXml = $nodoXML
$oXMLDocumentFiles.DocumentElement.AppendChild($FileNode)
}
It looks like you aren't using the cmdlet you think you are:
foreach( $file in (Get-Item 'c:\')) { ($file.FullName).Length }
is a condensed version of your variable to fill $fPath, but what it returns is a System.IO.FileSystemInfo object, of which there is a member called FullPath, a string.
What I assume you want it
foreach( $file in (Get-ChildItem 'c:\')) { ($file.FullName).Length }
which would return an Array of filepaths of the child items (or files within the folder)
This would also let you clean up a few things
$currentFilePath = $file.FullName
$fLength = $currentFilePath.Length
If I have an example function ...
function foo()
{
# get a list of files matched pattern and timestamp
$fs = Get-Item -Path "C:\Temp\*.txt"
| Where-Object {$_.lastwritetime -gt "11/01/2009"}
if ( $fs -ne $null ) # $fs may be empty, check it first
{
foreach ($o in $fs)
{
# new bak file
$fBack = "C:\Temp\test\" + $o.Name + ".bak"
# Exception here Get-Item! See following msg
# Exception thrown only Get-Item cannot find any files this time.
# If there is any matched file there, it is OK
$fs1 = Get-Item -Path $fBack
....
}
}
}
The exception message is ... The WriteObject and WriteError methods cannot be called after the pipeline has been closed. Please contact Microsoft Support Services.
Basically, I cannot use Get-Item again within the function or loop to get a list of files in a different folder.
Any explanation and what is the correct way to fix it?
By the way I am using PS 1.0.
This is just a minor variation of what has already been suggested, but it uses some techniques that make the code a bit simpler ...
function foo()
{
# Get a list of files matched pattern and timestamp
$fs = #(Get-Item C:\Temp\*.txt | Where {$_.lastwritetime -gt "11/01/2009"})
foreach ($o in $fs) {
# new bak file
$fBack = "C:\Temp\test\$($o.Name).bak"
if (!(Test-Path $fBack))
{
Copy-Item $fs.Fullname $fBack
}
$fs1 = Get-Item -Path $fBack
....
}
}
For more info on the issue with foreach and scalar null values check out this blog post.
I modified the above code slightly to create the backup file, but I am able to use the Get-Item within the loop successfully, with no exceptions being thrown. My code is:
function foo()
{
# get a list of files matched pattern and timestamp
$files = Get-Item -Path "C:\Temp\*.*" | Where-Object {$_.lastwritetime -gt "11/01/2009"}
foreach ($file in $files)
{
$fileBackup = [string]::Format("{0}{1}{2}", "C:\Temp\Test\", $file.Name , ".bak")
Copy-Item $file.FullName -destination $fileBackup
# Test that backup file exists
if (!(Test-Path $fileBackup))
{
Write-Host "$fileBackup does not exist!"
}
else
{
$fs1 = Get-Item -Path $fileBackup
...
}
}
}
I am also using PowerShell 1.0.