Powershell - Struggling to copy file from variable location - powershell

Ive been banging my head with a powershell script that i've tried a few differnt methods with and was wondering if someone can help?
Unsure at this point if im just doing something silly, thanks in advance
Aim:
To copy a vbs file from user's homedrives to a different location, the location of the vbs file changes depending on which user needs account admin doing hence why this needs to be variable. It gets the location from a text file which includes the exact path to go to and a destination which has already been created to copy the files to.
Where I have the issue currently:
$location = Get-Content C:\Users\Administrator\Desktop\here\drive4.txt | Out-String
$dest = "C:\Users\Administrator\Desktop\here"
Get-ChildItem -Path $location |
Copy-Item -Destination $dest -Recurse -Container -filter "peruser.vbs"
write-host $location
Read-Host -Prompt "Press Enter to continue or CTRL+C to quit"
The Issue
Please see the below, I have put write host to show the location powershell is trying to reach, as a side note I am able to reach this location fine through file explorer
Screenshot of error
Error Recieved
Get-ChildItem : Illegal characters in path.
At C:\Users\Administrator\Desktop\MapNetworkDrives2.ps1:17 char:1
+ Get-ChildItem -Path $location | Copy-Item -Destination $dest -Recurse -Container ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (\\WIN-7V7GI0R7C...
:String) [Get-ChildItem], ArgumentException
+ FullyQualifiedErrorId : ItemExistsArgumentError,Microsoft.PowerShell.Commands.GetChildItemCommand
Get-ChildItem : Cannot find path '\\WIN-7V7GI0R7CFK\homedrives\Onetest$
' because it does not exist.
At C:\Users\Administrator\Desktop\MapNetworkDrives2.ps1:17 char:1
+ Get-ChildItem -Path $location | Copy-Item -Destination $dest -Recurse -Container ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (\\WIN-7V7GI0R7C...
:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Unless I have misunderstood you - you are just trying to copy a file to various locations based on a list. If so you just need to loop through your list. Does this work for you?
$location = Get-Content "C:\Users\Administrator\Desktop\here\drive4.txt"
$dest = "C:\Users\Administrator\Desktop\here"
foreach ($loc in $location){
$vbs = (gci $loc -Filter "peruser.vbs").FullName
Copy-Item $vbs $dest -Force
}

You are not showing what is in the text file, forcing us to guess, which is not really a good thing relative to trying to help you. So, I am going to assume it's just a list of drive letters or path UNC's, which is really moot since you can pull those dynamically, thus no need for the file.
I don't understand why you are doing this...
Get-Content 'C:\Users\Administrator\Desktop\here\drive4.txt' |
Out-String
If it is just a text file for reading, why are you piping that anywhere?
You don't need double quotes for this. Single quotes for simple string, double for variable expansion or other specific formatted requirements
$dest = 'C:\Users\Adminstrator\Desktop\here'
Just pass the read directly
Get-Content 'C:\Users\Administrator\Desktop\here\drive4.txt' |
ForEach {
"Processing location $($PSItem.FullName)"
Copy-Item -Path $PSItem.FullName -Destination $dest -Filter 'peruser.vbs' -Recurse -WhatIf
}
Point of note:
You don't need Write-Host to output to the screen, as that is the PowerShell default, as I'll show in my example to follow. Really, except for writing screen text with color, you'd never need to use Write-Host/echo at all, well, there are a few other specific times to use it.
Also of note, regarding Write-Host from the inventor of Monad/PowerShell Jeffery Snover
http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful
https://twitter.com/jsnover/status/727902887183966208
https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Write-Information?view=powershell-5.1
You also, do not necessarily need both Get-ChildItem and Copy-Item, since both will read the folder tree recursively. As I'll show below. I am using splatting to tighten up the code block for readability.
So, if I demo this just using a drive and folder on my system. And step through a script build out to make sure I am getting what I expect at each step before moving to the next.
Get-PSDrive -PSProvider FileSystem |
Format-Table -AutoSize
<#
# Results
Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
C 357.48 118.83 FileSystem C:\ Windows\system32
D 114.10 362.71 FileSystem D:\ Scripts
E 1194.00 668.89 FileSystem E:\
F 3537.07 188.83 FileSystem F:\
G FileSystem G:\
#>
# Validate if there is any content in the destination
Try
{
Get-PSDrive -PSProvider FileSystem |
Where Name -eq 'D' |
ForEach {
"Processing location $($PSItem.Root) and the contents are as listed."
(Get-ChildItem -Path 'D:\Temp\here' -Recurse).FullName
}
}
Catch {Write-Warning -Message 'No content in the destination folder'}
<#
# Results
Processing location D:\ and the contents are as listed.
WARNING: No content in the destination folder
#>
# Show what is on the drive for the source files
Get-PSDrive -PSProvider FileSystem |
Where Name -eq 'D' |
ForEach{
"Processing the location $($PSItem.Root)"
(Get-ChildItem -Path "$($PSItem.Root)\Temp\book1.csv" -Recurse).FullName
}
<#
# Results
Processing the location D:\
D:\Temp\Source\book1.csv
D:\Temp\book1.csv
#>
<#
# Show what will happen if a Copy files from the source to the destination occurs
Using splatting for readability
#>
Get-PSDrive -PSProvider FileSystem |
Where Name -eq 'D' |
ForEach{
"Processing the location $($PSItem.Root)"
$CopyItemSplat = #{
Path = "$($PSItem.Root)\Temp\book1.csv"
Destination = "$($PSItem.Root)\Temp\here"
Recurse = $true
WhatIf = $true
}
}
Copy-Item #CopyItemSplat
<#
# Results
Processing the location D:\
What if: Performing the operation "Copy File" on target "Item: D:\Temp\book1.csv Destination: D:\Temp\here\book1.csv".
If the results are as expected, execute the action
Using splatting for readability
#>
Get-PSDrive -PSProvider FileSystem |
Where Name -eq 'D' |
ForEach{
"Processing the location $($PSItem.Root)"
$CopyItemSplat = #{
Path = "$($PSItem.Root)\Temp\book1.csv"
Destination = "$($PSItem.Root)\Temp\here"
Recurse = $true
}
}
Copy-Item #CopyItemSplat
<#
# Results
Processing the location D:\
#>
# Validate if there is any content in the destination
Try
{
Get-PSDrive -PSProvider FileSystem |
Where Name -eq 'D' |
ForEach {
"Processing location $($PSItem.Root) and the contents are as listed."
(Get-ChildItem -Path 'D:\Temp\here' -Recurse).FullName
}
}
Catch {Write-Warning -Message 'No content in the destination folder'}
<#
# Results
Processing location D:\ and the contents are as listed.
D:\Temp\here\book1.csv
#>

Related

How can I use a PowerShell output in the next command?

I am working on a PowerShell command to search across drives for a specific file. I am new to PowerShell so most of what I have already is just stuff I found online. At the moment I have this:
$ExclDrives = ('C')
>> Get-PSDrive -PSProvider FileSystem | Where-Object {$_.Name -notin $ExclDrives} `
>> | % {write-host -f Green "Searching " $_.Root;get-childitem $_.Root -include *MyFile.txt -r `
>> | sort-object Length -descending}
Which outputs this:
Searching D:\
Searching E:\
Searching F:\
Directory: F:\MyDirectory
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/13/2022 12:03 AM 0 MyFile.txt
PS C:\Windows\system32>
I would like to know how I can take the directory that is listed in the output and use it in a following command such as:
cd F:\MyDirectory
If this is possible through piping or something I would really appreciate an answer :)
Thanks for reading
I wasn't really sure what the best way to handle this would be if multiple files were found. We wouldn't be able to change directory into the parent folders while the script was running nor would we be able to do so for all of the returned files unless we opened new PowerShell windows for each. Since it appears that you will be searching for specific files which I assume will not return too many results and not knowing your ultimate goal I went with opening a new file explorer window for each file with the file being highlighted/selected.
$excludeDrives = ('C')
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Name -notin $excludeDrives } |
ForEach-Object {
Write-Host -f Green 'Searching ' $_.Root
Get-ChildItem -Path $_.Root -Recurse -Include *MyFile.txt -ErrorAction SilentlyContinue |
ForEach-Object {
# This line will open a file explorer window with the file highlighted
explorer.exe /select, $_
# This line will send the file object out through the pipeline
$_
} | Sort-Object Length -Descending
}
To answer your question about how to access the file's directory in the next command, you can use Foreach-Object and $_.Directory:
Get-ChildItem -Path $_.Root -Recurse -Include *MyFile.txt -ErrorAction SilentlyContinue |
Sort-Object Length -Descending |
ForEach-Object {
# Using the pipeline we can pass object along and access them
# using a special automatic variable called $_
# a property exists on FileInfo objects called Directory
'The directory is ' + $_.Directory
}
UPDATE
Hopefully this will answer the question in your comment
$ExclDrives = ('C')
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Name -in $ExclDrives } |
ForEach-Object {
Write-Host -f Green 'Searching ' $_.Root
Get-ChildItem $_.Root -Include *MyFile.txt -Recurse -ErrorAction SilentlyContinue |
ForEach-Object {
# do whatever you want with the file. Reference using $_
Write-Host "Found Filename: $($_.Name)`tDirectory: $($_.Directory)" -ForegroundColor Cyan
explorer.exe /select, $_
# output the fileinfo object, in this case
# to the next command in the pipeline which is Sort-Object
$_
} |
Sort-Object Length -Descending
}

Catching paths of inaccessible folders from Get-Childitem

I am working on small script to capture file hashes on a running system. I only have Powershell available.
This is the active part of the code:
get-childitem -path $path -filter $filename -Recurse -Force | Select FullName | foreach-object { get-filehash $_.fullname | select * }
this is the command I am testing with:
./Get-FileHashesRecursive.ps1 -path c:\ -filename *.txt
When running the script I get a series of errors because certain folders are inaccessible. I'd like to record the paths of those folders so the user has a record on completion of what failed.
the error looks like this in a console window:
get-childitem : Access to the path 'C:\$Recycle.Bin\S-1-5-21-4167544967-4010527683-3770225279-9182' is denied.
At E:\git\Get-RemoteFileHashesRecursive\Get-FileHashesRecursive.ps1:14 char:9
+ get-childitem -path $path -filter $filename -Recurse -Force | ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\$Recycle.Bin...3770225279-9182:String) [Get-ChildItem], UnauthorizedAccessException
+ FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
Is there a way I can grab the path or the entire first line of the error WITHOUT stopping the rest of the script from running?
As requested, here's my earlier comments as an answer:
Get-ChildItem -Path $Path -Filter $Filename -File -Recurse -Force -ErrorVariable FailedItems -ErrorAction SilentlyContinue | ForEach-Object { Get-FileHash -Path $_.FullName | Select-Object * }
$FailedItems | Foreach-Object {$_.CategoryInfo.TargetName} | Out-File "C:\Users\sailingbikeruk\Desktop\noaccess.log"
I have added the -File parameter to Get-ChildItem, because you are specifically dealing with only files.
I also added the -ErrorVariable and -ErrorAction parameters to the Get-ChildItem command. -ErrorVariable FailedItems defines a custom name for a variable which stores errors from the command during processing. -ErrorAction SilentlyContinue, tells the script to continue without notifying you of the errors.
Once your command has finished processing, you can parse the content of the $FailedItems variable. In the example above, I've output the TargetName to a file so that you can read it at your leisure, (please remember to adjust its file path and name as needed, should you also wish to output it to a file).

I have an issue with using Copy-Item and a Veriable for destination

This Might be an easy one. But I can figure out what is going wrong with my simple copy script.
I have a shared directory that I am copying items from. I am printing out the destination path to console so I know it is correct But I am receiving a powershell error I do not understand.
Here is my script
#Files to copy
#Get Installers from folder
$APPS = Get-ChildItem \\Server1\shared\APPS -Name
#ForEach loop to identify and move files
ForEach($APP in $APPS) {
$dest = "\\Server1\Shared\APPS\$APP"
#Write-host to see destination path on console
write-host $dest
#copy item from destination path to local directory
Copy-Item $dest -Destination "c:\apps\"
}
This seems straight forward. But I don't understand why I am receiving the following error
\\Server1\Shared\APPS\LTCDesktopSetup.exe
Copy-Item : The filename, directory name, or volume label syntax is incorrect.
At C:\Users\computer1\documents\PowerShell\Moving Installer to local drive.ps1:13 char:2
+ Copy-Item $dest -Destination "c:\apps\"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Copy-Item], IOException
+ FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Commands.CopyItemCommand
Augusto,
I'd suggest this syntax:
$APPS = (Get-ChildItem "\\mybooklive\CMShared\NAS-Downloads" -filter *.exe).FullName
ForEach ($App in $Apps) {
copy-item "$APP" -Destination "G:\Test\Copy-Test" -Force
}
or the more compact:
$APPS = (Get-ChildItem "\\mybooklive\CMShared\NAS-Downloads" -filter *.exe).FullName |
copy-item -Destination "G:\Test\Copy-Test" -Force
Getting FullName vs Name so you don't have to add the source path back in.
Using -Filter so you only get .exe files (this is an assumption from the variable name $Apps).
The force will take care of some IO problems like the file already existing.
HTH
Naturally, you'll have to substitute your paths for my test ones.

Install Printers from XML Files

I backed up printers from a Windows 10 system to XML files. I'm trying to add them using the Set-Printconfiguration CMDLET, but it seems to be not accepting variables?
I've looked everywhere but I cannot find anything saying my syntax is wrong.
#get list of printers in backup folder
$printerNames = (Get-ChildItem -Path c:\temp\printers\*.xml -Recurse | select name).name
foreach ($printer in $printerNames)
{
Set-PrintConfiguration -PrinterName $printer -PrintTicketXml c:\temp\printers\$printer
}
Here is the code I used to get the printer XML files:
$TARGETDIR = "c:\temp\printers"
if(!(Test-Path -Path $TARGETDIR )){
New-Item -ItemType directory -Path $TARGETDIR
}
# Get all the printers:
$PN = (get-printer | select name).name
# Foreach loop to create XML file for each printer configuration
Foreach ($P in $PN){
$GPC = get-printconfiguration -PrinterName $P
mkdir c:\temp\printers\$P
$GPC.PrintTicketXML | out-file C:\Temp\printers\$P.xml
# $p|select *|out-file -Path c:\temp\$p.xml -NoTypeInformation
}
edit: here is the error I'm getting:
Set-PrintConfiguration : The specified printer was not found.
At U:\PowerShell\Scripts\backup\newRestorePrinters.ps1:15 char:9
+ Set-PrintConfiguration -PrinterName $printer -PrintTicketXml ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (MSFT_PrinterConfiguration:ROOT/StandardCi...erConfiguration) [Set-PrintConfiguration], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070709,Set-PrintConfiguration
edit
I added more variables to get the list of printers w/out the .XML on the end of the names. It still gives me the same error output. It looks like it's not passing my variables to the set-printconfiguration command?
New restore script code:
$printerShortNameList = (Get-ChildItem -Path c:\temp\printers\*.xml -Recurse | select name).name
foreach ($shortName in $printerShortNameList)
{
$shortName.Replace('.xml', "x")
}
#get list of printers in backup folder
$printerNames = (Get-ChildItem -Path c:\temp\printers\*.xml -Recurse | select name).name
foreach ($printer in $printerNames)
{
Set-PrintConfiguration -PrinterName $shortName -PrintTicketXml c:\temp\printers\$printer
}
What was the error message? Shouldn't you take the '.xml' off the end of $printer for the printer name? I think you have to use add-printer first. I don't believe Set-PrintConfiguration creates printers.
On the bottom when you make the xml files, why do you create the c:\temp\printers\$p directory?
Set-PrintConfiguration:
You need administrator credentials to use Set-PrintConfiguration.

powershell compress files within subfolder and over certain age

I have been looking around for several hours on this issue. I am looking for some help to fix this script. The Script is to search the shared folder for files in sub-folders and zip the folder up in that sub folder. What I need to also do is add the last access time of the files or folder so I can set an age to it such as 1 year or 365 days. Then 7z would remove the file after compression saving space on the server for other things.
So c:\share
c:\share\folder1
c:\share\folder2
c:\share\folder3
etc...
the script in my testing is like this
<#
.SYNOPSIS
<A brief description of the script>
.DESCRIPTION
<A detailed description of the script>
.PARAMETER <paramName>
<Description of script parameter>
.EXAMPLE
<An example of using the script>
#>
#Compress all the files based on your folder structure
Set-Location -Path 'c:\shared'
Get-ChildItem -Path 'c:\shared' -Recurse | Where-Object {$_.PSIsContainer } | ForEach-Object {
$directoryFullName = $_.FullName
$directoryName = $_.Name
Invoke-Expression -Command 'C:\7-Zip\7z.exe a "-x!*.zip" -sdel $directoryFullName\$directoryName.zip $directoryFullName\*'
}
I am trying this below but getting an error I do not understand yet as to formatting property.
<#
.SYNOPSIS
<A brief description of the script>
.DESCRIPTION
<A detailed description of the script>
.PARAMETER <paramName>
<Description of script parameter>
.EXAMPLE
<An example of using the script>
#>
#Compress all the files based on your folder structure
Set-Location -Path 'c:\shared'
Get-ChildItem -Path 'c:\shared' -Recurse | Where-Object {$_.PSIsContainer $_.LastWriteTime -gt "01-01-1900" -and $_.LastWriteTime -lt (get-date).AddDays(-365) } | ForEach-Object {
$directoryFullName = $_.FullName
$directoryName = $_.Name
Invoke-Expression -Command 'C:\7-Zip\7z.exe a "-x!*.zip" -sdel $directoryFullName\$directoryName.zip $directoryFullName\*'
}
here is the error
At C:\Users\HarrelsonNetworks\Documents\windowspowershell\Scripts\testbackup3.ps1:14 char:75
+ ... Path 'c:\shared' -Recurse | Where-Object {$_.PSIsContainer $_.LastWri ...
+ ~~
Unexpected token '$_' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParseException
FullyQualifiedErrorId : UnexpectedToken
thanks
for your help
jharl
I would suggest the following: 1. Create a temp folder 2. MOVE (not copy) everything you want over to the temp folder 3. Zip the temp forder and save to desired location 3. delete the temp folder.
Breaking it out into steps like this will let you work through the problem better.
Here is something similar I wrote recently, it doesn't solve your problem but it should point you in the right direction:
function archiveLogs ( [string]$OriginalLocation, [string]$NewLocation ){
## Age of files to be archived
$DeleteOlderThan = 30
# Create temp folder to store files to be archived
New-Item c:\Temp\_tempfolder -type directory
# Get all children of specific directory older than 'X' days and of ".log" file type and move to temp folder
get-childitem -Path $OriginalLocation\*.log |
where-object {$_.LastWriteTime -lt (get-date).AddDays(-$DeleteOlderThan)} |
move-item -destination "C:\Temp\_tempfolder"
## Zip all .log files in temp folder and save to '$NewLocation\(DATE)_Archive'
& "C:\temp\7za.exe" a -tzip $NewLocation\$((Get-Date).ToString('yyyy-MM-dd HH-mm'))_Archive -r c:\Temp\_tempfolder\*.log
## Delete temp folder
Remove-Item c:\temp\_tempfolder -recurse -ErrorAction SilentlyContinue
Goodluck and let me know if you have any questions.
You're missing the logical operator -and. To resolve the error, change:
{$_.PSIsContainer $_.LastWriteTime -gt "01-01-1900" -and $_.LastWriteTime -lt (get-date).AddDays(-365) }
To:
{$_.PSIsContainer -and ($_.LastWriteTime -gt "01-01-1900") -and ($_.LastWriteTime -lt (get-date).AddDays(-365)) }