noobie here.
I have a csv file with two columns, they specify source files paths and destinated paths. I have around 1500+ lines to execute. Is there any way to batch process this via a bat file or anything else?
A line in my CSV looks like this:
Source Path,Dest Path
C:\Users\Nick\Pictures\XXXXXXX.img,C:\Users\Nick\Pictures\Export\XXXXXXX.img
I'd probably not solve this with batch files, but rather with PowerShell. It's possible in batch, but notoriously unreliable, especially around characters you don't expect when starting out.
In PowerShell this could be as simple as
Import-Csv files.csv | ForEach-Object {
Move-Item -LiteralPath $_.'Source Path' -Destination $_.'Dest Path'
}
You might need to create directories as required, perhaps something like this:
Import-Csv files.csv | ForEach-Object {
New-Item -ItemType Directory (Split-Path -Parent $_.'Source Path')
Move-Item -LiteralPath $_.'Source Path' -Destination $_.'Dest Path'
}
Related
I have multiple csv files in a folder.
I want to delete the first row of each csv in the folder using windows command line.
I am not familiar with windows command line so I will need information regarding how to call the folder within the console.
I do not want to make new files with the "subtracted" row, I just want to replace the original file or overwrite it.
Use Powershell
Get-ChildItem "path\to\your\directory" -Filter *.csv |
Foreach-Object {
$import = Get-Content $_.FullName
$import | Select-Object -Skip 1 | Set-Content $_.FullName
}
The txt file is just a bunch of UNC paths, i am trying to get a list of UNC paths from this text file put into another text file after the test-path is validated. it shows the validated paths on screen but the text file does not populate.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { write-host "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}
To complement Zam's helpful answer with background information:
Write-Host writes to the host[1] (typically, the console aka terminal), which bypasses PowerShell's success output stream and therefore sends nothing trough the pipeline.
See the bottom section of this answer for when Write-Host is appropriate; in short: you should generally only use it for display-only output.
Write-Output is the appropriate cmdlet for producing data output, but it is rarely necessary, because you can rely on PowerShell's convenient implicit output behavior, as shown in Zam's answer and explained in this answer.
Also, your command will perform much better if you simply pipe the % (ForEach-Object) command's output as a whole to a single Out-File call, rather than calling Out-File -Append for each input path.
Instead of using % with conditional explicit output, you can more elegantly implement your command with the Where-Object cmdlet:
Get-Content .\cfgpath.txt |
Where-Object { Test-Path $_ } |
Out-File -FilePath c:\temp\1.txt
Also note that for saving strings to a file it is more efficient to use Set-Content instead of
Out-File, though note that in Windows PowerShell the default output character encoding differs (no longer a concern in PowerShell [Core] 6+, which consistently defaults to BOM-less UTF-8); see this answer for when to choose which cmdlet.
By contrast, Out-File and > (its effective alias) use PowerShell's formatting system to write for-display representations of any non-string input objects to the output file, the same way that output renders to the display by default.
In other words: To save objects to a file in a way that is suitable for later programmatic processing, you need to use a structured file format, such as CSV (Export-Csv) or JSON (ConvertTo-Json, combined with Set-Content).
[1] In PowerShell 5.0 and higher, Write-Host now writes to a new stream, the information stream (number 6), which by default prints to the host. See about_Redirection.
Therefore, a 6> redirection now technically does allow you to send Write-Host output through the pipeline (though doing so is not a good idea) or capture / redirect it; e.g.,
Write-Host hi 6>&1 | % { "[$_]" }. Note that the type of the objects output by this redirection is System.Management.Automation.InformationRecord.
Write-Host only writes to the console. I believe what you want there is Write-Output.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { write-output "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}
Additionally you can just omit the Write-Output and that works too.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}
The code below is part of a switch and it's working fine, but the problem is: I need to change my file name to 15... Is it possible to to change it so that when I start it, it waits to select for a file with the tab key? Something like when you write Import-Csv in a PowerShell console and press Tab it shows all possbile paths and files.
$names = Import-Csv 15.csv -Header Givenname,Surname -Delimiter ";"
Write-Host "Rename your csv file to '15' and put it in same folder with this script" -ForegroundColor Cyan
pause
foreach ($Name in $Names) {
$FirstFilter = $Name.Givenname
$SecondFilter = $Name.Surname
Get-ADUser -Filter {GivenName -like $FirstFilter -and Surname -like $SecondFilter} |
select Enabled, SamAccountName, DistinguishedName,
#{n="ou";e={($_.DistinguishedName -split ",*..=")[2]}} |
Export-Csv .\sam.csv -NoTypeInformation -Append
}
So you want Intellisense in your script. Ambitious move. Most people would settle for the file browser dialog box. Anyway, I am going to have to refer you to smarter men than me. I was thinking ValidateSet attribute would serve your purpose but I realized that the traditional param block is not enough. So I looked up DynamicParams and this is what I found. This should work for you.
https://blogs.technet.microsoft.com/pstips/2014/06/09/dynamic-validateset-in-a-dynamic-parameter/
The simplest solution is to make your script accept the target file as an argument, by declaring a parameter:
param(
# Declare a mandatory parameter to which the file path of the CSV
# file to import must be passed as an argument on invocation.
[Parameter(Mandatory)]
[string] $FilePath
)
$names = Import-Csv $FilePath -Header Givenname,Surname -Delimiter ";"
foreach ($Name in $Names) {
$FirstFilter = $Name.Givenname
$SecondFilter = $Name.Surname
Get-ADUser -Filter {GivenName -like $FirstFilter -and Surname -like $SecondFilter} |
select Enabled, SamAccountName, DistinguishedName,
#{n="ou";e={($_.DistinguishedName -split ",*..=")[2]}} |
Export-Csv .\sam.csv -NoTypeInformation -Append
}
If you invoke your script without a file path, you will be prompted for it; let's assume your script is located in the current dir. and its name is someScript.ps1:
./someScript # invocation with no argument prompts for a value for $FilePath
Unfortunately, such an automatic prompt is not user-friendly and offers no tab completion.
However, on the command line PowerShell's tab completion defaults to completing file and directory names in the current location, so that:
./someScript <press tab here>
cycles through all files and directories in the current folder.
You can even type a wildcard expression and tab-complete that, if you don't know the full filename or don't want to type it in full:
./someScript *.csv<press tab here>
This will cycle through all *.csv files in the current dir. only.
If you want to go even further and customize tab completion to only cycle through *.csv files, you can use an [ArgumentCompleter({ ... })] attribute (PSv5+):
param(
[Parameter(Mandatory)]
# Implement custom tab-completion based on only the *.csv files in the current dir.
[ArgumentCompleter({
param($cmd, $param, $wordToComplete)
Get-ChildItem -Name "$wordToComplete*.csv"
})]
[string] $FilePath
)
# ...
Now,
./someScript <tab>
will cycle only through the *.csv files in the current directory, if any.
Caveat: As of PowerShell 7.0, tab-completing an argument for which the ArgumentCompleter script block returns no matches (in this case, with no *.csv files present) unexpectedly falls back to the default file- and directory-name completion - see this GitHub issue.
Similarly,
./someScript 1<tab>
will cycle only through the *.csv files in the current directory whose name starts with 1, if any.
As an alternative to using an attribute as part of a script's / function's definition, you can use the PSv5+ Register-ArgumentCompleter cmdlet to attach tab completions to the parameters of any command, i.e., including preexisting ones.
In PSv4- you have two (cumbersome) options for custom tab completion:
Use a dynamic parameter with a dynamically constructed [ValidateSet()] attribute - see the link in Rohin Sidharth's answer.
Customize the tabexpansion2 (PSv3, PSv4) / tabexpansion (PSv1, PSv2) function, but be sure not to accidentally replace existing functionality.
Below is my example.ps1 file that I use to write 1-off scripts. In your case I think you can get what you want with it. For example (no pun intended) you could call this script by typing
C:\PathToYourScripts\example.ps1 [tab]
where [tab] represents pressing the tab key. Powershell intellisense will kick in and offer autocompletion for file names. If your .csv file is not in the current director you can easily use Powershell intellisense to help you find it
C:\PathToYourScripts\example.ps1 C:\PathToCSvFiles[tab]
and powershell will autocomplete. Would-be-downvoters might notice that powershell autocomplete is definitely NOT a complete file-picker but this seems to fulfill the intent of the asked question. Here's the sample.
<#
.NOTES
this is an example script
.SYNOPSIS
this is an example script
.DESCRIPTION
this is an example script
.Example
this is an example script
.LINK
https://my/_git/GitDrive
#>
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact="Low")]
param (
[string] $fileName
)
Begin {
}
Process {
if ($PSCmdlet.ShouldProcess("Simulated execution to process $($fileName): Omit -Whatif to process ")) {
Write-Information -Message "Processing $fileName" -InformationAction Continue
}
}
End {
}
If you want to get autocomplete help for multiple parameters just type in the parameter name(s) and press [tab] after each one. Note that leaving the parameters blank will not break the script but you can either extend this to mark the parameters required or just fail with a helpful message. That seems a bit beyond the original question so I'll stop here.
I got multiple csv files, all with the same header, all in the same folder which I have to combine to create one large csv file. Since every .csv-file has the same header, I guess using it once should be enough.
All files look like this (also delimited by ','):
Header1,Header2,Header3
Data1, Data2, Data3
Data4, Data5, Data6
Can you help out? I'm not very comfortable with Powershell yet, tried out different codes but nothing really helped me out.
Thanks
if all csv's has the same columns, simply:
# Original CSV
$csv = import-csv c:\temp.csv
# 2nd CSV
$csv2 = import-csv c:\temp2.csv
# As simple as:
$csv += $csv2
If you want to import all CSV's in the current folder you can do something like the following:
Get-ChildItem *.csv | % { Import-Csv $_ }
start by copying the first file to an output file.
then see this (question, answer):
https://stackoverflow.com/a/2076557/1331076
it shows you how to remove the first line from a file.
you would have to modify it, so instead of replacing your existing file, you Add-Content to the output file.
I'm trying to write a PowerShell script which refers to a CSV file for a list of banned file names (mostly games) and removes them from my users' home folders. I've got it working in the root or target directory, but cannot seem to make it recursive, so that it drills-down through subfolders, despite trying to utilise the -recurse parameter.
As you'll see, I'm not much of a coder, but am trying to learn and better myself. My PS script looks like this:
cd "C:\Test user"
Import-Csv "C:\Games.csv" | foreach {Remove-Item $_.Game -Verbose -Recurse}
and my CSV file looks like this:
Game,Game1.swf,Game2.swf,Game3.swf
Any advice as to what I am missing in order to make this work recursively would be hugely appreciated. Thank you all in advance for being so generous with your time.
You're misunderstanding the structure of CSVs. Essentially CSV is a way to store tabular data (data organized in rows and columns). Your file would have to have the following structure if you want it to work with the code you posted:
Game
Game1.swf
Game2.swf
Game3.swf
The data you posted is just a comma-separated list of values. To process this string you need something like this:
(Get-Content "C:\Games.csv") -split ',' | Remove-Item -Verbose -Recurse
or perhaps like this (if you want to skip the first element):
(Get-Content "C:\Games.csv") -split ',' |
Select-Object -Skip 1 |
Remove-Item -Verbose -Recurse
Edit: If you need to recursively search a folder tree for files from your CSV and then delete just the files you'd do it like this:
$root = 'C:\root\folder'
$items = (Get-Content "C:\Games.csv") -split ',' | Select-Object -Skip 1
Get-ChildItem $root -Recurse -Force |
Where-Object { $items -contains $_.Name } |
Remove-Item -Verbose