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
Related
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)"
So I'm new to PowerShell, and I'm trying to get this function to work.
I have 2 ValidateSet arrays with 3 parameters. These parameters are supposed to change the file path and copy them over from one server to another. For some reason, I keep getting the command prompt for the parameters instead of them passing through. I'm guessing it's an issue with the ForEach-Object, but I'm at a loss. It IS, however, working for the $ArchivePath. I'm new, so please be gentle... TIA
param(
[Parameter(Mandatory = $true)]
[ValidateSet("One", "Two", "Three")]
[string[]]$Channel
,[Parameter(Mandatory = $true)]
[Alias('Phase')]
[ValidateSet("Devl", "Test", "Prod")]
[string[]]$Phase
,[Parameter(Mandatory = $false)]
[string]$FilenameFilter = '.csv'
,[Parameter(Mandatory = $false)]
[switch]$CreateTrigger
)
function ExitWithCode { param($exitcode) $host.SetShouldExit($exitcode); exit $exitcode }
$exitcode = 0
try {
# Get a list of files on the host server.
#
$files = Get-ChildItem -File -Path "\\ServerName\d\Extract\$Phase\FileTransfer\$Channel\Outbound"
# Destination directory.
#
$LocalPath = "\\ServerName\d\Extract\$Phase\FileTransfer\$Channel\Outbound" #for testing
# Set up folder name for Archive server. Formated as YYYYMMDDTHHMMSS YYYYMMDD --> Var_Date, 'T' --> Var_Constant & HHMMSS --> Var_Time
#
$Var_Date = get-date -UFormat "%Y-%m-%d"
$Var_Constant = 'T'
$Var_Time = get-date -UFormat "%H-%M-%S"
$Var_Fulldate = $Var_Date + $Var_Constant + $Var_Time
$ArchivePath = $env:USERPROFILE + "\Desktop\$Channel\$Var_Fulldate" #For testing
New-Item -Type Directory -Path $ArchivePath
if (-not (Test-Path -Path $ArchivePath -ErrorAction SilentlyContinue)) { $ArchivePath = $Env:TEMP }
#Look for files in Outbound directory and remove
Get-ChildItem -File -Path $LocalPath | ForEach-Object { Copy-Item $_.FullName } #Using copy instead of remove for test
$FileCount = 0
Write-Output Try2 #for testing
pause #for testing
foreach ($file in $files) {
if ((-not $file.IsDirectory) -and ($File.FullName -match $FilenameFilter)) {
$localfilename = $LocalPath + $file.Name
if (Test-Path $localfilename) { Copy-Item $localfilename }
try {
Copy-Item -Path $(Join-Path -Path $LocalPath -ChildPath $file.Name) -Destination $ArchivePath
#Remove files from outbound since they've been archived
#
#Remove-Item -Path $file.FullName
"Retrieved file $file"
$FileCount++
}
catch {
Write-Output Try13 #for testing
$exitcode = 13
"failed to retrieve $file"
}
finally {
$error.Clear()
}
}
}
}
catch {
Write-Output Try3
$exitcode = 14
}
finally {
Write-Output Try4
$error.Clear()
}
if ($CreateTrigger -and ($exitcode -eq 0) -and ($FileCount -gt 0)) {
New-Item -Path "$LocalPath\Trigger_File.trg" -ItemType File | Out-Null
}
#ExitWithCode $exitcode # take out for testing
The output:
PS C:\Users\me> \\Server\blah\USERS\me\My Documents\Folder\Get-FileName_t4.ps1
cmdlet Get-FileName_t4.ps1 at command pipeline position 1
Supply values for the following parameters:
Channel[0]: Three
Channel[1]:
Phase[0]: Devl
Phase[1]:
Directory: C:\Users\me\Desktop\Three
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/22/2019 12:17 PM 2019-11-22T12-17-23
Try2
Press Enter to continue...:
Retrieved file File1_20191122080912.csv
Retrieved file File2_20191122080922.csv
Retrieved file File3_20191122080925.csv
Retrieved file File4_20191122080932.csv
Retrieved file File5_20191122080933.csv
Retrieved file File6_20191122080933.csv
Try4
You are getting prompted because you're not passing in those parameters but Mandatory=$true is set on the arguments you are getting prompted for. Since your session is interactive, it asks you to input the correct values. If you don't want to get prompted, provide the mandatory arguments:
"\\Server\blah\USERS\me\My Documents\Folder\Get-FileName_t4.ps1" -Channel Three -Phase Dev1
A couple of other things I noticed:
You don't need to provide Mandatory=$false, as Mandatory is $false by default
Setting an alias of Phase for the -Phase argument is also redundant
Quick question regarding the PowerShell Copy-Item command. I was wondering if you have a directory structure and wanted to overwrite another directory structure is there a way to run the Copy-Item command in a 'preview' mode. It would output what files its overwriting from directory a to directory b but not actually perform the Copy-Item command.
Any help or advice appreciated.
Thanks.
Interesting question!
Here is my attempt of doing it all in Powershell, so not needing RoboCopy.
function Copy-Preview {
[CmdletBinding(DefaultParameterSetName = 'ByPath')]
param(
[Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByPath', Position = 0)]
[ValidateScript({ Test-Path $_ })]
[string]$Path,
[Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByLiteralPath', Position = 0)]
[ValidateScript({ Test-Path $_ })]
[string]$LiteralPath,
[Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 1)]
[string]$Destination,
[string]$Filter = $null,
[string]$Include = $null,
[string]$Exclude = $null,
[switch]$Recurse,
[switch]$Force
)
if ($PSCmdlet.ParameterSetName -eq 'ByLiteralPath') { $Path = $LiteralPath }
# determine if $Path and $Destination hold a file or a directory
$srcIsFolder = (Test-Path $Path -PathType Container -ErrorAction SilentlyContinue)
# cannot use Test-Path here because then the path has to exist.
# assume if it has an extension the path is a file, otherwise a directory
# NOTE:
# This is certainly not fullproof, so to avoid problems, always make sure
# the destination ends with a backslash if a directory is intended.
$destIsFolder = (-not ([System.IO.Path]::HasExtension($Destination)))
if ($destIsFolder -and !(Test-Path $Destination -PathType Container)) {
Write-Host "Destination path does not exist yet. All files from '$Path' will be copied fresh" -ForegroundColor Green
return
}
elseif ($srcIsFolder -and (!$destIsFolder)) {
# should not happen: source is a directory, while the destination is a file..
Write-Error "When parameter Path points to a directory, the Destination cannot be a file.."
return
}
$count = 0
if ($srcIsFolder -and $destIsFolder) {
# Both the source and the destinations are folders
# make sure both paths are qualified for .Replace() further down
if (-not $Path.EndsWith("\")) { $Path += "\" }
if (-not $Destination.EndsWith("\")) { $Destination += "\" }
$splat = #{
Filter = $Filter
Include = $Include
Exclude = $Exclude
Recurse = $Recurse
Force = $Force
}
# add either Path or LiteralPath to the parameters as they are mutually exclusive
if ($PSCmdlet.ParameterSetName -eq 'ByPath') { $splat.Path = $Path }
else { $splat.LiteralPath = $LiteralPath }
$srcFiles = Get-ChildItem #splat | Select-Object -ExpandProperty FullName
# reuse the splat parameters hash for the destination, but change the Path
if ($splat.LiteralPath) {($splat.Remove("LiteralPath"))}
$splat.Path = $Destination
$destFiles = Get-ChildItem #splat | Select-Object -ExpandProperty FullName
foreach ($srcItem in $srcFiles) {
$destItem = $srcItem.Replace($Path, $Destination)
if ($destFiles -contains $destItem) {
Write-Host "'$destItem' would be overwritten"
$count++
}
}
}
elseif (!$srcIsFolder) {
# the source is a file
if (!$destIsFolder) {
# the destination is also a file
if (Test-Path $Destination -PathType Leaf) {
Write-Host "'$Destination' would be overwritten"
$count++
}
}
else {
# source is file, destination is a directory
$destItem = Join-Path $Destination (Split-Path $Path -Leaf)
if (Test-Path $destItem -PathType Leaf) {
Write-Host "'$destItem' would be overwritten"
$count++
}
}
}
$msg = "$count item{0} would be overwritten by Copy-Item" -f $(if ($count -ne 1) { 's' })
$dash = "-" * ($msg.Length)
Write-Host "$dash`r`n$msg" -ForegroundColor Green
}
tl;dr:
Copy-Item -WhatIf will not give you the level of detail you're looking for - see below.
Use robocopy.exe -l instead (Windows only), as Ansgar Wiechers recommends, because it individually lists what files would be copied, including dynamically omitting those already present in the target dir (with the same size and last-modified time stamp, by default).
Generally, robocopy is faster and more fully featured than Copy-Item, and it avoids a notable pitfall of the latter.
Get-Help about_CommonParameters documents the -WhatIf common parameter supported by many (but not all) cmdlets, whose purpose is to preview an operation without actually performing it.
However, this feature is implemented in an abstract fashion, and often doesn't provide information as detailed as one would hope.
Case in point: while Copy-Item does support -WhatIf, it probably won't give you the level of detail you're looking for, because if the source item is a directory, only a single line such as the following is output:
What if: Performing the operation "Copy Directory" on target "Item: sourceDir Destination: destDir".
Note that you'll see the same line whether or not your Copy-Item call includes the -Recurse switch.
Even if you ensure existence of the target directory manually and append /* to the source directory path in order to see individual filenames, you'd only see them at the child level, not further down the subtree, and you'd never get the dynamic information that robocopy -l provides with respect to what files actually need replacement.
You can use the -whatif parameter.
Copy-item -path myfile.txt -destination myfoldertoCopyTo\ -whatif
I am using PowerShell copy files to multiple locations, then do a check after copy done, I want to send a message on failure if one or more files does not exist on one of the destinations.
$SRCDIR1 = "C:\temp\Copy\00_S"
$DST = "C:\temp\Copy\01_D","C:\temp\Copy\02_D","C:\temp\Copy\03_D"
$File_list = Get-ChildItem -Path $SRCDIR1\*.xml
$DST | %{ Copy-Item $SRCDIR1\*.xml -Destination $_ }
Foreach ($item in $File_list) {
If (Test-Path $DST ) {
Write-Host $item exists in $DST
}
else {
Write-Host $item DOES NOT exists in $DST
}
}
It works when all files are in all destinations, but if I delete different files from different destinations testing "write-host if not exist", it still "write-host" everything exists.
On failure, i want it "write-host" which file doest not exist on which destination separately. How can I modify the code?
Update made it working. has to be loop in loop....
$FileList = Get-ChildItem -Path $SRCDIR1\*.xml | Select -ExpandProperty Name
Foreach ($item in $FileList){
$DST |
% {if (Test-Path ($_ + "\" + "$item")){
write-host $item exist in $_ -ForegroundColor Green
}else{
write-host $item does not exist in $_ -ForegroundColor Red
}
}
}
Your $DST is an Array of Strings and this is the "problem", as Test-Path will return one boolean value for each path in that array:
PS> $DST = "C:\temp\Copy\01_D","C:\temp\Copy\02_D","C:\temp\Copy\03_D"
PS> Test-Path $DST
True
True
False
Which means if(Test-Path $DST) will be the same as if(#($true, $true, $false)). Let's see how they are evaluated:
PS> if ($false) {"HA!"} else {"Ney"}
Ney
PS> if (#($false, $false)) {"HA!"} else {"Ney"}
HA!
As you can see, if ($variable) evaluates to $true, when $variable is any non-null value which is not $false, so even #($false, $false, $false) will evaluate to $true.
Don't check $DST, instead check for $item's existence:
if (Test-Path $item) {
Write-Host $item exists in $DST
}
I have this code :
$Count=0
Function DryRun-UploadFile($DestinationFolder, $File, $FileSource, $Count)
{
if($FileSource -eq $null){
$FileSource = $Folder
}
$path= [String]$FileSource+'\'+$File
$Size = get-item $Path
$Size = $Size.length
if($Size -lt 160000){
Write-Host "Passed"
}else{
$Count=$Count+1
}
}
function DryRun-PopulateFolder($ListRootFolder, $FolderRelativePath, $Count)
{
Write-Host "Uploading file " $file.Name "to" $WorkingFolder.name -ForegroundColor Cyan
if(!($File -like '*.txt')){
#Upload the file
DryRun-UploadFile $WorkingFolder $File $FileSource $Count
}else{
$Count=$Count+1
}
}
}
Function DryRun-Copy-Files{
$AllFolders = Get-ChildItem -Recurse -Path $Folder |? {$_.psIsContainer -eq $True}
#Get a list of all files that exist directly at the root of the folder supplied by the operator
$FilesInRoot = Get-ChildItem -Path $Folder | ? {$_.psIsContainer -eq $False}
#Upload all files in the root of the folder supplied by the operator
Foreach ($File in ($FilesInRoot))
{
#Notify the operator that the file is being uploaded to a specific location
Write-Host "Uploading file " $File.Name "to" $DocLibName -ForegroundColor Cyan
if(!($File -like '*.txt')){
#Upload the file
DryRun-UploadFile($list.RootFolder) $File $null $Count
}else{
$Count=$Count+1
}
}
#Loop through all folders (recursive) that exist within the folder supplied by the operator
foreach($CurrentFolder in $AllFolders)
{
DryRun-PopulateFolder ($list.RootFolder) $FolderRelativePath $Count
}
Write-output "Number of files excluded is: "$Count | Out-file DryRun.txt -append
}
I have removed some of my code for simplicity sake as it has nothing to do with my problem. My code goes through a file structure and counts up if the file is above 160000 bytes or is a txt file. run calling DryRun-Copy-Files.
And I have a variable called $count which I want to use in all the functions and then output what the count is to a file.
The problem is it only counts in the first function DryRun-Copy-Files not in the others
define the variable with global:
$global:count=0
and use it in the functions (don't explicit pass it)