I'm having an issue while searching with PowerShell over the network; program get stuck while executing Get-ChildItem.
# creating search string
$date = "*2018-01-10*"
$format = ".avi"
$toSearch = $date + $format
echo $toSearch
# Verifying A: drive to be disconnected
net use /d A: /y
# connecting Network drive to local drive A:
if (Test-Connection -ComputerName PROC-033-SV -Quiet) {net use A: \\PROC-033-SV\c$}
# getting list of directories to search on
$userList = Get-ChildItem -Path A:\users\*.* -Directory
# verifying list of directories prior to search
echo $userList
# searching through Network, on A:\Users\ subdirectories for $toSearch variable
Get-ChildItem -Path $userList -Include $toSearch -Recurse -Force
# *** HERE's where the program get stuck, it nevers stop searching
# it nevers reach pause
pause
Does anyone know why Get-ChildItem keeps looping and it never stops?
I'm using PS v4 no -Depth option available for -Recurse parameter; I'm suspecting that might be the issue.
If you want to limit recursion depth in PowerShell v4 and earlier you could wrap Get-ChildItem in a custom function, e.g. like this:
function Get-ChildItemRecursive {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[string[]]$Path = $PWD.Path,
[Paramter(Mandatory=$false)]
[string]$Filter = '*.*',
[Parameter(Mandatory=$false)]
[int]$Depth = 0
)
Process {
Get-ChildItem -Path $Path -Filter $Filter
if ($Depth -gt 0) {
Get-ChildItem -Path $Path |
Where-Object { $_.PSIsContainer } |
Get-ChildItemRecursive -Filter $Filter -Depth ($Depth - 1)
}
}
}
Related
currently I am working on a script that should give me the number of files that have been modified in the last 30 days or between 30 and 180 days or that have not been modified for 180 days. So three queries in total, everything gets its own variable in which the count is stored.
The whole thing should be executed on several remote servers.
However, the folder structure on these servers is very deep and PowerShell regularly hits the 260 character limit (PathTooLongException).
The following is the script:
$servers = "server1", "server2"
$folder = "\Path\to\folder\"
foreach ($server in $servers) {
$session = New-PSSession -ComputerName $server
$folder_30 = Invoke-Command -Session $session -ScriptBlock {
(Get-ChildItem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -ge (Get-Date).AddDays(-30)}).count
}
$folder_30_180 = Invoke-Command -Session $session -ScriptBlock {
(Get-Childitem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -ge (Get-Date).AddDays(-180) -AND $_.LastWriteTime -lt (Get-Date).AddDays(-30)}).count
}
$folder_180 = Invoke-Command -Session $session -ScriptBlock {
(Get-Childitem -LiteralPath $using:folder -Recurse -File | where{$_.LastWriteTime -lt (Get-Date).AddDays(-180)}).count
}
Remove-PSSession $session
}
Is there a way to change the script so that the "PathTooLongException" error no longer occurs? Would it be an option to change the script so that it first jumps to the lower levels and only then executes the "Get-ChildItem" and count cmdlt and how would I do that? Or is there a more elegant solution?
Thanks in advance :)
Edit: the remote servers are running win server 2012
Edit 2: I found a solution which is not perfect and takes like twice as long, but I can avoid the PathTooLongException;
You can use the following code snippet:
Get-Childitem -LiteralPath \\?\UNC\SERVERNAME\folder\
This didn't work with the Invoke-Command on the remote servers because they are running an old PowerShell version, but I was able to use this command and remotely connect from a Win 2016. Not perfect, but it works ¯_(ツ)_/¯
This link helped me: https://c-nergy.be/blog/?p=15339
You could probably do faster using Robocopy.
Suppose the local path on each server is 'C:\Somewhere\folder', then for each server you need to use the UNC path \\servername\C$\Somewhere\folder.
The easiest way of doing that is to create a template folder path where only the servername needs to be inserted:
$servers = "server1", "server2"
$folder = '\\{0}\C$\Somewhere\folder' # template UNC path whwere te servername or IP is inserted
foreach ($server in $servers) {
$path = $folder -f $server
$allFiles = #((robocopy $path NULL /L /E /NP /XJ /NC /NS /NDL /NJH /NJS /TS /R:0 /W:0) -replace '^\s+' -ne '' |
Select-Object #{Name = 'LastWriteTime'; Expression = {([datetime]($_ -split "`t")[0]).ToLocalTime()}},
#{Name = 'FullName'; Expression = {($_ -split "`t")[1]}})
# get the number of files newer than 30 days ago
$date = (Get-Date).AddDays(-30).Date
$folder_30 = ($allFiles | Where-Object { $_.LastWriteTime -ge $date }).Count
# get the number of files older than 180 days ago
$date = (Get-Date).AddDays(-180).Date
$folder_180 = ($allFiles | Where-Object { $_.LastWriteTime -lt $date }).Count
# output an object with the properties needed
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
Path = $path
AllFiles = $allFiles.Count
NewerThan30Days = $folder_30
OlderThan180Days = $folder_180
Between30And180Days = $allFiles.Count - $folder_30 - $folder_180
}
}
Because the -Exclude parameter of Get-ChildItem is not filtering on subfolders when using the -Recurse flag, see among other unable-to-exclude-directory-using-get-childitem-exclude-parameter-in-
powershell
But the -Exclude parameter can be used to filter out folders on the root level
I wrote my own recursive function:
function Get-ChildItem-Recurse() {
[cmdletbinding()]
Param(
[parameter(ValueFromPipelineByPropertyName = $true)]
[alias('FullName')]
[string[]] $Path,
[string] $Filter,
[string[]] $Exclude,
[string[]] $Include,
[switch] $Recurse = $true,
[switch] $File = $false
)
Process {
ForEach ( $P in $Path ) {
Get-ChildItem -Path $P -Filter $Filter -Include $Include -Exclude $Exclude | ForEach-Object {
if ( -not ( $File -and $_.PSIsContainer ) ) {
$_
}
if ( $Recurse -and $_.PSIsContainer ) {
$_ | Get-ChildItem-Recurse -Filter $Filter -Exclude $Exclude -Include $Include -Recurse:$Recurse
}
}
}
}
}
When I pipe the result to a ForEach-Object to Copy the result to a different destination, all works fine, and the items except those that match the exclude parameter are copied
$source = 'D:\Temp\'
$destination = 'D:\Temp_Copy\'
Get-ChildItem-Recurse -Path $source -Exclude #( '*NotThis*', '*NotThat*' ) | ForEach-Object {
$_ | Copy-Item -Destination ( "$($destination)$($_.FullName.Substring($source.Length))" ) -Force
}
When I pipe it directly to the Copy-Item commandlet, I get a null-valued error, because .Substring() is called on $_.FullName that is apparently null
Get-ChildItem-Recurse -Path $source -Exclude #( '*NotThis*', '*NotThat*' ) |
Copy-Item -Destination ( "$($destination)$($_.FullName.Substring($source.Length))" ) -Force
Because the native commandlet Get-ChildItem does allow me to pipe its result to Copy-Item, I like my own custom function also to be able to do that. But I can't figure out why it's not working.
Use a scriptblock to dynamically bind a piped input value to the parameter:
Get-ChildItem ... |Copy-Item -Destination { "$($destination)$($_.FullName.Substring($source.Length))" }
The following answer by mklement0 has extensive details on this kind of dynamic binding (retroactively named "delay-bind scriptblocks", or colloquially "pipeline-bound scriptblocks"):
For PowerShell cmdlets, can I always pass a script block to a string parameter?
Usually you pipe the source to copy:
$source = 'D:\Temp\'
$destination = 'D:\Temp_Copy\'
get-childitem $source | copy-item -destination $destination -whatif
So, I actually don't believe that its the string value I'm passing to the parameter that is the issue, I think its how my for loop is displaying it.
Function Get-Users{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true,
HelpMessage='Enter Computer Name')]
[Alias('CN','Computer','Name')]
[ValidateLength(1,15)]
[string]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true)]
[String]$Exclude
)
$UNC = Get-ChildItem -Path \\$ComputerName\c$\Users -Exclude $Exclude -Directory | Select-Object -ExpandProperty Name | Sort-Object -Descending
for($i=0; $i -lt $UNC.Count; $i++){
"$($i): $($UNC[$i])" }
""
$SS = Read-Host -Prompt "Enter # of user(s)"
$s = $SS -split " "
foreach($user in $UNC[$s]){
"$User" }
}
I feel like this should work as it does when I do it when just running it in the console like so: Get-ChildItem -Path C:\users -Exclude "Abraham" -Directory; It simply excludes my directory listing.
Even when nothing is specified in the parameter it still runs just fine: Get-ChildItem -Path C:\users -Exclude "" -Directory in regards to listing all directories, but when I pass it using my self-made parameters it doesn't display correctly. What I get instead is the following:
PS > Get-Users -Exclude Abraham
0: P
Enter # of user(s):
When it should be:
PS > Get-Users -Exclude Abraham
0: Public
Enter # of user(s):
Can someone educate me on why it doesn't display the full Directory name?
Please note that itll list all directories when the -Exclude Parameter isn't specified.
Change this $UNC = Get-ChildItem -Path \\$C...... for this $UNC = #(Get-ChildItem -Path \\$C......) or this [array]$UNC = Get-ChildItem -Path \\$C.......
The problem is that you're getting just one result from gci, because of that the variable is a string and not an array, hence when calling the position 0 of your string you're getting the first letter of the string and not the first element of your array :)
PS /~> $unc=(gci *.txt).Name
PS /~> for($i=0; $i -lt $unc.count; $i++)
{
"$($i): $($UNC[$i])"
}
0: t
PS /~> [array]$unc=$unc
PS /~> for($i=0; $i -lt $unc.count; $i++)
{
"$($i): $($UNC[$i])"
}
0: test.txt
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
I am trying to archiving old files in the server (older then 90 days) and it should be separate ZIP file for every month. I have powershell-v1.0 so I am not able to use System.Management.Automation.PSObject
I have created a script but I have a problem with zip file name. When I run the script all files are moving to one archive with name +++.
$folders ="C:\New folder\test\"
Function Zip
{
Param
(
[string]$zipFile
,
[string[]]$toBeZipped
)
$CurDir = Get-Location
Set-Location "C:\program files\7-zip\"
.\7z.exe A -tzip $zipFile $toBeZipped | Out-Null
Set-Location $CurDir
}
$files = Get-ChildItem -Path $folders | Where-Object {$_.LastwriteTime -lt ((Get-date).adddays(-90))} | % { $_.FullName};
if ( !$files)
{break}
else
{
Write-Host $files
$file_year = $files.LastwriteTime.Year
$file_month = $files.LastwriteTime.Month
echo $file_month
ZIP $folders+"$file_year"+"$file_month"+".zip" $files
If(Test-Path $folders+$file_year+$file_month+.zip)
{
Remove-Item $files
}}
It would be nice if someone can figure out what am I doing wrong.
There are two issues why this doesn't work. First, you are selecting (using the ForEach-Object cmdlet %) the FullName property thus won't be able to access the LastWriteTime property anymore. Secondly, you are trying to access the property on a potential array of files (which year and month you want to use?)
So I would change / refactor your script to something like this (untested).
$folders ="C:\New folder\test\"
function Write-ZipFile
{
Param
(
[string]$Path,
[string[]]$Files
)
$7zip = Join-Path $env:ProgramFiles '\7-zip\7z.exe'
& $7zip A -tzip $zipFile $toBeZipped | Out-Null
}
$files = Get-ChildItem -Path $folders |
Where-Object { $_.LastWriteTime -lt ((Get-date).AddDays(-90)) }
$zipFile = Join-Path $folders ('{0}{1}.zip' -f $files[0].LastwriteTime.Year, $files[0].LastwriteTime.Month)
Write-ZipFile -Path $zipFile -Files ($files | select -ExpandProperty FullName)
The list of file names to archive is in $files. Use $files.GetType() to see that it is an array. Use $files[0].GetType() to see that each element is a string type, not a file object type.
$files = Get-ChildItem -Path $folders |
Where-Object {$_.LastwriteTime -lt (Get-date).adddays(-90)}
I imagine that you will want to omit directories.
The $files array will be an array of FileInfo objects, not strings.
Secondly, do something to iterate over the list of FileInfo objects.
[cmdletbinding()]
Param()
$folders = "H:\src\powershell\afm"
Function Zip
{
Param (
[string]$zipFile
, [string[]]$toBeZipped
)
& "C:\Program Files\7-Zip\7z.exe" A -tzip $zipFile $toBeZipped | Out-Null
}
$files = Get-ChildItem -Path $folders -File |
Where-Object {($_.LastwriteTime -lt (Get-date).adddays(-10)) -and ($_.Extension -ne ".zip")}
$files | ForEach-Object {
Write-Verbose "Working on file $_"
$file_year = $_.LastwriteTime.Year
$file_month = $_.LastwriteTime.Month
Write-Verbose "file_month is $file_month"
Zip "$folders\$file_year$file_month.zip" "$folders\$_"
If (Test-Path "$folders\$file_year$file_month.zip")
{
### Remove-Item $_q
}
}
It would appear that the problem is that there is nothing to process each file to archive them. There is no ForEach-Object.
There is no LastWriteTime on an array object, only on a FileInfo object.