Powershell Copy-Item not keeping folder structure - powershell

I have the below script to copy data from a local folder to a remote folder created with the current date. However the files are copying but the folder structure is not.
$Date = (Get-Date).ToString("MMddyyyy"),$_.Extension
$Source = "E:\Folder1\\*"
$Dest = "\\Server\Share\Folder2"
$Username = "Username"
$Password = ConvertTo-SecureString "Password" -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential($Username, $Password)
Remove-PSDrive -Name T
Start-Sleep -s 1
New-PSDrive -Name T -PSProvider FileSystem -Root $Dest -Credential $mycreds -Persist
if (!(Test-Path "T:\$Date"))
{
md -Path "T:\$Date"
}
Get-ChildItem -Path $Source -Recurse | % { Copy-Item -Path $_ -Destination "T:\$Date" -Container -Force -Verbose }
Could anyone advise where I am going wrong here?
Thank you.

Nice script, I think we can get this sorted in no time!
Why this happened
The reason this is failing is in this step right here:
Get-ChildItem -Path $Source -Recurse
The -Recurse switch is causing you pain. To illustrate why this is, I created a simple folder structure.
When you run Get-ChildItem -Path $Source -Recurse alone, you'll get a recursive listing of all files in the $Source path, like so:
PS C:\temp\stack> Get-ChildItem -Recurse
Directory: C:\temp\stack
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 8/4/2017 10:50 AM Source
Directory: C:\temp\stack\Source
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 8/4/2017 10:57 AM 1
d----- 8/4/2017 10:57 AM 2
d----- 8/4/2017 10:57 AM 3
d----- 8/4/2017 10:57 AM 4
Directory: C:\temp\stack\Source\1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/4/2017 10:57 AM 20 Archive.rar
-a---- 8/4/2017 10:56 AM 0 File01.bmp
Well, in the next step of your script, you pipe that output over to Copy-Item for every single file.
You're basically expressly telling PowerShell 'take this folder and all of it's subfolders and everything, and dump them all in this one folder, ignoring the folder structure'
How to fix
What you really want to do is simply move the -Recurse parameter over to Copy-Item, and you're done :)
Get-ChildItem -Path $Source |
Copy-Item -Destination "T:\$Date" -Container -Recurse -Force -Verbose
Hope that helps, have a nice 🎃day!

This question is a few years old, but in case someone arrives here like I did...
In another post, a user suggested using robocopy. It worked great for me:
robocopy "source/folder" "target/folder" "*.file_extension" /s
This command copies from the source folder the files that match the given filter, replicating the folder structure in the destination folder.
In this case, it would be used like:
robocopy "E:\Folder1\" "\\Server\Share\Folder2" "*" /s

Related

Powershell Get-childitem script not saves output on Windows 10 machines or when pushed remotely

I have written a simple script that should collect all local documents from folders on C: drive (*.pdf, *.jpg, *.xls, *.doc) and save them to the "computername" text file under the C:\TEMP folder.
I have been able to launch this locally on Windows 11 machine without issues! When I tried to do the same thing on Windows 10 machine text file was empty!
I have narrow this down to the user account interaction, as it seems on Windows 10 machines. If this script is launched from PowerShell running as current user on Windows 10 it seems the script generates output. If it is Run as Administrator (locally or via remote push as admin) I got empty file. I would appreciate any guidance.
code:
$FindDate=(Get-Date).adddays(-180)
[System.IO.Directory]::CreateDirectory('C:\TEMP')
{Get-ChildItem -Path C:\ -Directory} |
Where-Object Name -NotIn #('Windows','Program Files','$Recycle.Bin','Windows.old') |
% { Get-ChildItem -Include *.doc,*.docx,*.pdf,*.jpg,*jpeg,*.xls,*.xlsx -File -
Recurse} -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -ge $FindDate } |
Select LastWriteTime,Name,Directory |
Out-File C:\TEMP\$env:Computername.txt
there are some issues with the current code:
e.g. you do:
{Get-ChildItem -Path C:\ -Directory}
this defines a expression and will not execute:
PS C:\Users\vcxy> {Get-ChildItem -Path C:\ -Directory}
Get-ChildItem -Path C:\ -Directory
PS C:\Users\vcxy>
you want probably:
PS C:\Users\vcxy> Get-ChildItem -Path C:\ -Directory
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 8/15/2022 2:46 PM AMD
d----- 6/5/2021 2:10 PM PerfLogs
d----- 4/19/2022 6:56 PM plc_debug
d-r--- 9/22/2022 6:26 PM Program Files
d-r--- 6/12/2022 5:02 PM Program Files (x86)
d----- 9/27/2022 6:43 PM TEMP
d----- 9/27/2022 5:06 PM tmp
d-r--- 10/6/2021 12:44 PM Users
d----- 4/8/2020 2:18 PM VM
d----- 9/22/2022 10:46 PM Windows
I'll updated your version as needed:
$FindDate=(Get-Date).adddays(-180)
#This works but you could also do new-item C:\temp -ItemType Directory
[System.IO.Directory]::CreateDirectory('C:\TEMP')
$folders = Get-ChildItem -Path C:\ -Directory | Where-Object {$_.Name -Notmatch 'Windows|Program Files|$Recycle\.Bin|Windows\.old'}
$files = #(
$folders | % {
Get-ChildItem -Path $_.fullname -Include *.doc,*.docx,*.pdf,*.jpg,*jpeg,*.xls,*.xlsx -File -Recurse -ErrorAction:SilentlyContinue | Where-Object {$_.LastWriteTime -ge $FindDate}
}
)
#You should use export-csv, its better for later processing of the data
If ($files){
$files | Select LastWriteTime,Name,Directory | export-csv C:\TEMP\$env:Computername.txt
}
It will only create the output file if files are found, no files no output file or in regards to the original version, no files no content in the output file.

Powershell Copy-Item -recurse does not pick up new directory

I'm creating a binary PS module with PlatyPS help. I have a local poor-man's deploy script like this (PS 5.1):
$modulepath= "$Env:USERPROFILE\Documents\WindowsPowerShell\Modules"
$releasePath = ".\bin\release\net472"
# build project
dotnet build -c release
# build documentation
# requires PlatyPS module
New-ExternalHelp -Path .\docs -OutputPath $releasePath\en-US -Force
ls $releasePath # debug
# copy files
Get-ChildItem -Path $releasePath | Copy-Item -Destination $modulepath\PoshCommence -Recurse -Force
ls $modulepath\PoshCommence # debug
This ouputs the following surprising result:
Directory: X:\CustomModules\PoshCommence\bin\release\net472
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 17-2-2021 00:31 en-US
-a---- 17-2-2021 00:24 36352 PoshCommence.dll
Directory: C:\Users\XXX\Documents\WindowsPowerShell\Modules\PoshCommence
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 17-2-2021 00:31 871 about_PoshCommence.help.txt <- from en-US folder
-a---- 17-2-2021 00:24 36352 PoshCommence.dll
-a---- 17-2-2021 00:31 141080 PoshCommence.dll-Help.xml <- from en-US folder
None of the directories exist prior to running the script. I deleted the 'bin' project folder as well as the 'PoshCommence' module folder.
It seems either Get-ChildItem or Copy-Item -Recurse do not pick up the newly created 'en-US' directory, but the contents of it do get copied to the root level. If I run the script a second time (without deleting folders), it works as expected (except I still have docs stuff in the root of the module I don't want).
That has me stumped. I have tried -Verbose on everything, I put Start-Sleep after every line thinking operations may need time, but to no avail. Why isn't the 'en-US' folder picked up the first time?
Answering my own question. My confusion stems from the fact that
Copy-Item -Path $source -Destination $destination -Recurse
Works differently depending on whether the folder in $destination already exists or not.
So in the end all it took was
if (!(Test-Path $modulepath\PoshCommence)) { mkdir $modulepath\PoshCommence }
prior to the copying.

How do I overwrite a file using PowerShell?

New-Item -Path "C:\aws" -Name "script.ps1" -ItemType "file" -Value "text in file"
I am trying to create a file using the above command, if the file already exists I have to replace it with new file, it will have same file name. Please help
Use the force — specifically, the -Force parameter...
New-Item -Path "C:\aws" -Name "script.ps1" -ItemType "file" -Value "text in file" -Force
Whether script.ps1 already exists or not, upon success it will contain the exact content text in file.
Its use is also demonstrated in example #9 of the New-Item documentation...
Example 9: Use the -Force parameter to overwrite existing files
This example creates a file with a value and then recreates the file using -Force. This overwrites The existing file and it will lose it's content as you can see by the length property
PS> New-Item ./TestFile.txt -ItemType File -Value 'This is just a test file'
Directory: C:\Source\Test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/1/2020 8:32 AM 24 TestFile.txt
New-Item ./TestFile.txt -ItemType File -Force
Directory: C:\Source\Test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/1/2020 8:32 AM 0 TestFile.txt
Omitting -Force from the second invocation of New-Item produces the error New-Item : The file '...\TestFile.txt' already exists..

Remove-Item -Recurse -Force can't remove symlinks

It seems that Remove-Item -Recurse -Force cannot deal with removing symlinks. How do I recursively delete everything from a given directory with symlinks made all over the place?
MWE:
PS C:\Users\Administrator\Desktop\test> mkdir foo
Directory: C:\Users\Administrator\Desktop\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 7/30/2020 11:58 AM foo
PS C:\Users\Administrator\Desktop\test> mkdir bar
Directory: C:\Users\Administrator\Desktop\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 7/30/2020 11:58 AM bar
PS C:\Users\Administrator\Desktop\test> New-Item -ItemType SymbolicLink -Path foo -Name bar -Value C:\Users\Administrator\Desktop\test\bar
Directory: C:\Users\Administrator\Desktop\test\foo
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----l 7/30/2020 11:59 AM bar
PS C:\Users\Administrator\Desktop\test> Remove-Item -Recurse -Force foo
Remove-Item : There is a mismatch between the tag specified in the request and the tag present in the reparse point
At line:1 char:1
+ Remove-Item -Recurse -Force foo
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Remove-Item], Win32Exception
+ FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.RemoveItemCommand
The actual CMD equivalent to Remove-Item -Recurse -Force is:
cmd /c rmdir /s /q foo
as it will delete symlinks also in subdirectories. This seems to be good enough for me.
Looks like you are running into the same issue as mentioned here:
https://github.com/powershell/powershell/issues/621
And workaround which is mentioned in this thread is:
Get-ChildItem $somepath -Attributes ReparsePoint | % { $_.Delete() }
Or you can gather more information here:
https://github.com/PowerShell/PowerShell/pull/11331
There's always cmd. Wmi and powershell can't even delete profiles these days because of links.
cmd /c del bar

Powershell - Get-ChildItem wildcard

Get-ChildItem filename*.log.* fetches filename*.log as well. How can I get only the log files ending with dot extension filename*.log.* so I can delete them? I want to use Remove-Item but decided to check using get-childItem.
Here is the files.
Server1234.log
Server1234.log.1
Server1234.log.2
Server1234.log.3
Server1234.log.4
Get-ChildItem filename*.log.* shows all of the above. I don't want Server1234.log in the output.
Your filter should work. I have created 3 files:
New-Item 'Server1234.log' -ItemType File
New-Item 'Server1234.log.1' -ItemType File
New-Item 'Server1234.log.2' -ItemType File
And here is the output of Get-ChildItem Server1234*.log.*
PS D:\> Get-ChildItem Server1234*.log.*
Directory: D:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 13/05/2020 10:51 0 Server1234.log.1
-a---- 13/05/2020 10:51 0 Server1234.log.2
Note: The filter parameter of the Get-ChildItem cmdlet doesn't use regex! If you want to use regex you can do this within the Where-Object cmdlet:
Get-ChildItem | Where-Object { $_.Name -match 'Server123.*log\.+' }
You might use the filter
Get-ChildItem filename*.log.?*
The question mark states that at least one character has to be there...