I am new to powershell. I create a powershell script which need to search a string in the path provided in parameters and replace that string. But actually it is replacing entire file content with new string.
I am using Powershell in Windows 10 OS.
Code:
param(
[Parameter(Mandatory=$true, ParameterSetName="Path", Position=0,HelpMessage='Data folder Path')]
[string] $Path,
[Parameter(Mandatory=$true, HelpMessage='Input the string to be replaced')]
[string] $Input,
[Parameter(Mandatory=$true,HelpMessage='Input the new string that need to be replaced')]
[string] $Replace
)
$a = Test-Path $Path
IF ($a -eq $True) {Write-Host "Path Exists"} ELSE {Write-Host "Path Doesnot exits"}
$configFiles = Get-ChildItem -Path $Path -include *.pro, *.rux -recurse
$Append = join-path -path $path \*
$b = test-path $Append -include *.pro, *.rux
If($b -eq $True) {
foreach ($file in $configFiles)
{
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace [regex]::Escape($Input), $Replace } |
Set-Content $file.PSPath
}
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,"Done",0x0)
}
As best I can read this without directly reproducing it, this is where it goes wrong:
(get-content $file.pspath) gets the entire content of the file, not its name.
Your "foreach" then regexes every line in the file, and finally "set-content" replaces the contents of the file, not its path.
If you want to change the name of a file, you are looking for Rename-Item, not Set-Content. If you want the name of a file $file.Name will do, you don't need Get-Content, which will ... get its content :)
This should be a working solution.
Param(
[Parameter(Mandatory,
ParameterSetName='Path',
Position=0,
HelpMessage='Data folder Path')]
[String]
$Path,
[Parameter(Mandatory,
HelpMessage='Input the string to be replaced')]
[String]
$StringToReplace,
[Parameter(Mandatory,
HelpMessage='Input the new string that need to be replaced')]
[String]
$ReplacementString
)
If (!(Test-Path $Path)) {
Write-Host 'Path does not exist'
Return
}
Get-ChildItem -Path $Path -Include *.pro,*.rux -Recurse |
? { $_.Name -like "*$StringToReplace*" } |
% { Rename-Item $_ $($ReplacementString+$_.Extension) }
(New-Object -ComObject Wscript.Shell).Popup("Operation Completed",0,"Done",0x0)
Related
I'm writing a script where I'm trying to output the results from a Get-ChildItem command to a log file. The script below is simplified to show the issue I'm having. For example, the WriteLog function is used several times in the actual Script. The file listing is not the only thing to be added to the log file.
The snippet below writes a long run-on line of all full filenames to the log.
$FilePath = "G:\Folder"
$LogPathName = "G:\Folder\TestLog.log"
Function WriteLog {
Param ([string]$LogString)
$Stamp = Get-Date
$LogMessage = "$Stamp - $LogString"
Add-Content $LogPathName -value $LogMessage
}
$FileList = Get-ChildItem –Path $FilePath -include ('*.csv', '*.xlsx')
writelog $FileList
I want each filename to begin on a new line--like a list. How can I do this?
Leaving your function WriteLog as is, the workaround is to iterate over each element of the array returned by Get-ChildItem so that the function appends to the file line-by-line:
foreach($item in $FileList) {
WriteLog $item
}
However a more elegant way of approaching this would be to leverage ValueFromPipeline, then you could simply pipe Get-ChildItem into your function and let the process block handle each element. You can also add a -PassThru switch to it in case you also want the same object be returned as output for later manipulation. And lastly, it may be worth adding a new -Path parameter to make it reusable.
function Write-Log {
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object] $InputObject,
[parameter(Mandatory)]
[string] $Path,
[parameter()]
[switch] $PassThru
)
begin { $sb = [System.Text.StringBuilder]::new() }
process {
$sb = $sb.AppendLine("$(Get-Date) - $InputObject")
if($PassThru.IsPresent) { $InputObject }
}
end { Add-Content $Path -Value $sb.ToString() -NoNewline }
}
$FileList = Get-ChildItem -Path .\* -Include '*.csv', '*.xlsx' |
Write-Log -Path "G:\Folder\TestLog.log" -PassThru
Working to configure a conversion script where replacements are done in files based on what is in the CSV file. Like a dictionary or lookup, rather than directly storing in the code.
i.e.
File1.txt
Jim
Tim
Jan
Greg
Mark
CSV File
DEV,UAT,PROD
John,Jimothy,Timothy
Jimothy,Frank,Karen
Jim,Max,Lisa
So if converting DEV > UAT file1.txt would replace Jim with Max:
Max
Tim
Jan
Greg
Mark
Below is where I'm at currently convertReferences.ps1
#Declare Function Library
. $PSScriptRoot\functionLibrary.ps1
#Get Variables
$global:Dictionary = Import-Csv -Path $PSScriptRoot\IDDictionary.csv
#Input Location
$InputLocation = Read-Host 'Enter Input Location' ['DEV/UAT']
If(!(test-path $PSScriptRoot\$InputLocation))
{
New-Item -ItemType Directory -Force -Path $PSScriptRoot\$InputLocation
}
#Get Output Location
$OutLocation = Read-Host 'Enter an Output Location' ['UAT/PROD']
If(!(test-path $PSScriptRoot\$OutLocation))
{
New-Item -ItemType Directory -Force -Path $PSScriptRoot\$OutLocation
}
#Call Function Convert-DEV
if ($InputLocation -eq 'DEV'){
$Files | convert-DEV -InputLocation $InputLocation -OutLocation $OutLocation}
else {Write-host "NO VALID INPUT DECLARED - PLEASE RUN AGAIN" -ForegroundColor RED
<# Action when all if and elseif conditions are false #>
}
The Function itself is below:
function convert-DEV {
[cmdletbinding()]
param(
#Input Path
[Parameter(Mandatory)]
[ValidateSet('DEV')]
[string]
$InputLocation,
#Files
[Parameter(Mandatory, ValueFromPipeline)]
[string]
$Files,
#Output Path
[parameter()]
[ValidateSet('UAT')]
[string]
$OutLocation
)
process{
Write-host "Replacing Variables in: " $Files -ForegroundColor Blue
$Staging = $Files
(Get-Content $Files | foreach {$_ -replace $Global:Dictionary.DEV , $Global:Dictionary.UAT}) |
Set-Content $Files
(Get-Content $Staging | foreach {Copy-Item -Path $Staging -Destination $PSScriptRoot\$OutLocation})
Write-host "UPDATED File has been copied to: " -ForegroundColor Red $PSScriptRoot\$OutLocation `n `n
}
}
Any thoughts on how to reach my desired output?
You can use this function to get you started, this is assuming the CSV and the File are placed in the same location:
$csv = Import-Csv .\ReferenceTable.csv
function Replace {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $File,
[Parameter(Mandatory)]
[ValidateSet('DEV', 'UAT', 'PROD')]
[string] $InputLocation,
[Parameter(Mandatory)]
[ValidateSet('DEV', 'UAT', 'PROD')]
[string] $OutLocation,
[Parameter(Mandatory)]
[object[]] $ReferenceTable
)
begin {
$map = #{}
foreach($i in $ReferenceTable) {
$map[$i.$InputLocation] = $i.$OutLocation
}
}
process {
foreach($line in (Get-Content $File).Trim()) {
if($map.ContainsKey($line)) {
$map[$line]
continue
}
$line
}
}
}
Get-ChildItem .\File1.txt | Replace -InputLocation DEV -OutLocation UAT -ReferenceTable $csv
After some further work and the help from Santiago above I have the below which works as I wanted:
function Start-Replace {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $File,
[Parameter(Mandatory)]
[ValidateSet('DEV','UAT','PROD')]
[string] $InputLocation,
[Parameter(Mandatory)]
[ValidateSet('DEV', 'UAT', 'PROD')]
[string] $OutLocation,
[Parameter(Mandatory)]
[object[]] $ReferenceTable
)
begin {
#Create Map Hashtable/array
$map = #{}
#For each row in Reference list select the value in the input column and add to map array. After match and add output column value to array
foreach($row in $ReferenceTable)
{
$map[$row.$InputLocation] = $row.$OutLocation
}
}
#Replace
process {
$outname = split-path $file -leaf
$input = Get-Content $file
Foreach ($key in $map.Keys) {
$input = $input.Replace($key, $map.$key)
}
Set-Content -Path $PSScriptRoot\$OutLocation\$outlocation-$outname -value $input
write-host $outlocation'-'$outname
}
}
Get-ChildItem -Filter *_* | Foreach-Object -Process {
$NewName = [Regex]::Match($_.Name,"^[^ _]*").Value +'.jpg'
$_ | Rename-Item -NewName $NewName
}
I have been using this code to rename files
For example, 569_SOM_TEST.jpg to 569
but if there is 569_TOB_TEST.jpg, It gives an error Rename-Item : Cannot create a file when that file already exists.
I want it to make it 569-1.
How can I accomplish this?
To prevent renaming clashes, you can put the below helper function at the top of your script:
function Rename-FileUnique {
# Renames a file. If a file with that name already exists,
# the function will create a unique filename by appending '(x)' after the
# name, but before the extension. The 'x' is a numeric value.
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $true, Position = 1)]
[string]$NewName,
[switch]$PassThru
)
# Throw a bit nicer error than with [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
if (!(Test-Path -Path $Path -PathType Leaf)){
Throw [System.IO.FileNotFoundException] "Rename-FileUnique: The file '$Path' could not be found."
}
# split the new filename into a basename and an extension variable
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($NewName)
$extension = [System.IO.Path]::GetExtension($NewName) # this includes the dot
$folder = Split-Path -Path $Path -Parent
# get an array of all filenames (name only) of the files with a similar name already present in the folder
$allFiles = #(Get-ChildItem $folder -Filter "$baseName*$extension" -File | Select-Object -ExpandProperty Name)
# for PowerShell version < 3.0 use this
# $allFiles = #(Get-ChildItem $folder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename / strip the path from the file name
$NewName = $baseName + $extension # or use $NewName = Split-Path $NewName -Leaf
if ($allFiles.Count) {
$count = 1
while ($allFiles -contains $NewName) {
$NewName = "{0}-{1}{2}" -f $baseName, $count++, $extension
}
}
Write-Verbose "Renaming '$Path' to '$NewName'"
Rename-Item -Path $Path -NewName $NewName -Force -PassThru:$PassThru
}
and use it like:
Get-ChildItem -Filter '*_*.jpg' | Foreach-Object {
# create the proposed new filename
$newName = '{0}.jpg' -f ($_.Name -split '_')[0]
$_ | Rename-FileUnique -NewName $newName
}
This will ensure that any proposed new filename gets a not already used index number attached to its basename.
I am trying to list all msi files found in a given dir to a file, then extract those msi files. I am trying to use the $_ in the foreach-object to pass the path however it seems to be interpreting it as a literal, instead of passing the path. I thought the $_ would pass the object, which in this case would be the filepath, but it doesnt seem to be functioning that way. What is the proper way to pass the found filepaths to the Export-MsiContents function?
Function Export-MsiContents
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true, Position=0)]
[ValidateNotNullOrEmpty()]
[ValidateScript({Test-Path $_})]
[ValidateScript({$_.EndsWith(".msi")})]
[String] $MsiPath,
[Parameter(Mandatory=$false, Position=1)]
[String] $TargetDirectory
)
if(-not($TargetDirectory))
{
$currentDir = [System.IO.Path]::GetDirectoryName($MsiPath)
Write-Warning "A target directory is not specified. The contents of the MSI will be extracted to the location, $currentDir\Temp"
$TargetDirectory = Join-Path $currentDir "Temp"
}
$MsiPath = Resolve-Path $MsiPath
Write-Verbose "Extracting the contents of $MsiPath to $TargetDirectory"
Start-Process "MSIEXEC" -ArgumentList "/a $MsiPath /qn TARGETDIR=$TargetDirectory" -Wait -NoNewWindow
}
$Dir = get-childitem d:\temp\test -recurse
$List = $Dir | where {$_.extension -eq ".msi"}
$List | format-table fullname | out-file d:\temp\test\msilist.txt
$List | ForEach-Object {Export-MsiContents -MsiPath $_}
It looks like you are trying to specify a childitem object as the $MsiPath which should be a string.
So you would need to specify which value in that object to use as $MsiPath. In this case it looks like you would like to pass the fullname.
Try this:
$List | ForEach-Object {Export-MsiContents -MsiPath $_.fullname}
I am a newbie in PowerShell, and trying to learn the things from few forums and msdn. Now i got some requirement from my group of learners.
I am trying to compare 2 folder's files with each other in powershell, for effective file comparison i am using MD5 Hashes.
Till now i have created a code like this,
[Cmdletbinding()]
Param
(
[Parameter(Position=0, Mandatory)][ValidateScript({ Test-Path -Path $_ })][string]$SourceFolder,
[Parameter(Position=1, Mandatory)][ValidateScript({ Test-Path -Path $_ })][string]$DestinationFolder
)
$SourceFolderList =#()
$DestinationFolderList =#()
$Sourcefiles = #(Get-ChildItem -Path $SourceFolder -Filter *.log)
foreach($srcFile in $Sourcefiles )
{
$SourceFolderHash = [ordered]#{}
$SourceFolderHash.Name = $srcFile.Name
$SourceFolderHash.FullName = $srcFile.FullName
$obj = New-Object PSObject -Property $SourceFolderHash
$SourceFolderList+= $obj
}
$Destfiles = #(Get-ChildItem -Path $DestinationFolder -Filter *.log)
foreach($Destfile in $Destfiles )
{
$DestinationFolderHash = [ordered]#{}
$DestinationFolderHash.Name = $Destfile.Name
$DestinationFolderHash.FullName = $Destfile.FullName
$obj = New-Object PSObject -Property $DestinationFolderHash
$DestinationFolderList+= $obj
}
$SourceFolderList =#() &
$DestinationFolderList =#() are Arrays with Name & FullName properties.
Now i am trying to create a new array with values which matches in the $SourceFolderList & $DestinationFolderList ( I hope i am going in the right way?!)
But the problem is, i am not sure how to loop through each item in the Arrays and get the fullnames of each file from 2 folders to pass as params to MD5hash Function.
I am trying in this way
##1
For ($i =$j=0; $i -le $SourceFolderList.Count -and $j -le $DestinationFolderList.Count; $i++ -and $j++)
{
$file1Name = $SourceFolderList[$i].Name
$file1Path = $SourceFolderList[$i].FullName
$file2Name = $DestinationFolderList[$j].Name
$file2Path = $DestinationFolderList[$j].FullName
}
##2
foreach( $file in $SourceFolderList)
{
if($DestinationFolderList.Name -contains $file.Name )
{
Write-Host $file.Name -ForegroundColor Cyan
Write-Host $DestinationFolderList.($file.Name).FullName -ForegroundColor Yellow
}
}
In the 1st way i am not getting correct File Paths << Index is mismatching for Destination folder's file paths >>
In the 2nd Way i am not at all getting the Full Path of file.
Please correct me if am going in the wrong way to achieve my requirement.
And please help me to solve this issue.
I think your're making your task more difficult that it is, by gathering file info into the arrays. Why don't you just iterate over the files in the source folder and compare their hashes with hashes of files in the destination folder on the fly:
function Compare-Folders
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$Source,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[string]$Destinaton,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$Filter
)
Process
{
# Iterate over files in source folder, skip folders
Get-ChildItem -Path $Source -Filter $Filter | Where-Object {!$_.PsIsContainer} | ForEach-Object {
# Generate file name in destination folder
$DstFileName = Resolve-Path -Path (Join-Path -Path $Destinaton -ChildPath (Split-Path -Path $_.FullName -Leaf))
# Create hashtable with filenames and hashes
$Result = #{
SourceFile = $_.FullName
SourceFileHash = (Get-FileHash -Path $_.FullName -Algorithm MD5).Hash
DestinationFile = $DstFileName
DestinationFileHash = (Get-FileHash -Path $DstFileName -Algorithm MD5).Hash
}
# Check if file hashes are equal and add result to hashtable
$Result.Add('IsEqual', ($Result.SourceFileHash -eq $Result.DestinationFileHash))
# Output PsObject from hashtable
New-Object -TypeName psobject -Property $Result |
Select-Object -Property SourceFile, SourceFileHash , DestinationFile, DestinationFileHash, IsEqual
}
}
}