I'm using the following code to load SQL scripts from a folder and execute them.
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object) {
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\ngo\r?\n' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
I've been asked if its possible to have some sort of table of contents to execute these files in a particular sequence without having to rename them. Is it possible to have a comma delimited file that I could loop through and load each file in the same sequence?
Edit
This is the code I think I'm going to go with:
Get-Content $executionOrder
ForEach ($file in $executionOrder) {
$sqlScript = $pathToScripts + "\" + $file
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript -Encoding UTF8 | Out-String) -split '\r?\ngo\r?\n' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
Is it possible to have a comma delimited file that I could loop through and load each file in the same sequence
Yes. You just need to update your outer look logic to account for that input. With only minor changes you can get what you want.
foreach ($sqlScript in (Import-CSV $pathtoCSV)){
# Process file.
}
That would work if you wanted a CSV file input as you requested. In comments it looks like you are getting a static list of file names in a predefined directory.
$pathToFileList = "C:\Bagel.txt"
$rootScriptDirectory = "\\path\to\scripts"
$removeComments = $true
Get-Content $pathToFileList | ForEach-Object{
# Build the full file paths
$scriptFilePath = [io.path]::Combine($rootScriptDirectory,$_)
# If this file actually exists then it should be processed
If(Test-Path $scriptFilePath -PathType Leaf){
# Get the file contents
$fileContents = Get-Content $scriptFilePath -Encoding UTF8 | Out-String
# Clean the file contents as required
if($removeComments){
$queries = $fileContents -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$'
} else {
$queries = $fileContents -split '\r?\ngo\r?\n'
}
# Execute each query of the file
$queries | ForEach-Object{
$SqlCmd.CommandText = $_.Trim()
$reader = $SqlCmd.ExecuteNonQuery()
}
# Hilarity ensues
} else {
Write-Warning "Could not locate the file '$scriptFilePath'"
}
}
The features of switch are a little wasted here since you only have two states. Move the things that actually get changes into an if block. Get the file list and test that the file exists. Open it and parse the queries from it with your already set logic.
Related
I'm trying to add a line in a .sln file before the $pattern. The only problem is that when I try to add the $firstOccurrence condition in the if statement it doesn't add anything at all. It still triggers the Write-Debug.
The first occurrence part is now commented out but I can't seem to find out why it doesn't write anything when I set the first occurrence.
Original source to my solution can be found here:
How to declare a variable and its type is Boolean in PowerShell?
$firstOccurrence = $true;
$pattern = "Global"
(Get-Content $fileName) | Foreach-Object {
#if ($firstOccurrence) {
if ($_ -match $pattern) {
Write-Debug "test"
$firstOccurrence = $false
#Add Lines after the selected pattern
"aaaaaaaaaaaaaaaaaaaaaaa"
}
#}
# send the current line to output
$_
} | Set-Content $fileName
You could also do this using (very fast) switch -Regex like:
$fileName = 'D:\Test\blah.txt'
$firstOccurrence = $true
$pattern = "Global"
$insert = "aaaaaaaaaaaaaaaaaaaaaaa"
$newContent = switch -Regex -File $fileName {
$pattern {
if ($firstOccurrence) {
$insert
$firstOccurrence = $false
}
$_
}
default { $_ }
}
$newContent | Set-Content $fileName -Force
Or did you perhaps mean this:
$fileName = 'D:\Test\blah.txt'
$pattern = "Global"
$insert = "aaaaaaaaaaaaaaaaaaaaaaa"
((Get-Content -Path $fileName -Raw) -split $pattern, 2) -join "$insert $pattern" | Set-Content $fileName -Force
?
I have the following foreach loop in a powershell script:
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object) {
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
The entire script is wrapped with a catch/try block and works OK for errors.
I now have a requirement that if one of the files(scripts) produces an error, the loop will ignore that and move on to the next file.
Could I do this with nested catch try blocks or is there a way to resume the loop on an error?
Adding an additional Try..Catch would probably be the way to go. For example you could modify your ForEach-Object blocks as follows:
Try {
$SqlCmd.CommandText = $_.Trim();
$reader = $SqlCmd.ExecuteNonQuery()
} Catch {
Write-Error "$($SqlCmd.CommandText) resulted in an error"
$_
}
Note the $_ within the Catch block would contain the error that occurred. After the Catch is executed the rest of the script should then carry on as normal.
Use the ErrorAction preference as SilentlyContinue wherever there is an executing statement or a statement which you feel is likely to throw an error. In you current script, you can do this -
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object)
{
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments)
{
$true
{
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' -ErrorAction SilentlyContinue |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false
{
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' -ErrorAction SilentlyContinue |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
See this and this link for details.
I have written a powershell script which compares words from a text-file with a csv-column, and if the word in the column matches, the line is deleted.
My code works great, but it is very slow, due to saving and copying the csv file after every line. Is there a way improving this?
$reader = [System.IO.File]::OpenText($fc_file.Text)
try {
for() {
$line = $reader.ReadLine()
if ($line -eq $null) { break }
if ($line -eq "") { break }
# process the line
$fc_suchfeld = $fc_ComboBox.Text
$tempstorage = $scriptPath + "\temp\temp.csv"
Import-Csv $tempfile -Delimiter $delimeter -Encoding $char | where {$_.$fc_suchfeld -notmatch [regex]::Escape($line)} | Export-Csv $tempstorage -Delimiter $delimeter -Encoding $char -notypeinfo
Remove-Item $tempfile
Rename-Item $tempstorage $tempfile_ext
}
}
finally {
$reader.Close()
}
I have this script:
# Read in the RESOURCE ID values I want to locate
$TextToFind = Get-Content -Path .\ResourceIDs.txt
$Text = ""
$PathArray = #()
$Results = ".\ResultsResourceIDs.txt"
# Now iterate each of these text values
$TextToFind | ForEach-Object {
$Text = $_
Write-Host "Checking for: " $Text
If ((Get-Content .\Resources.rc) | Select-String -Pattern $Text) {
$PathArray += $Text + "¬Found"
}
Else {
$PathArray += $Text + "¬Not Found"
}
}
Write-Host "Contents of ArrayPath:"
$PathArray | ForEach-Object {$_}
$PathArray | % {$_} | Out-File $Results
It works fine. But the resulting text file has content like:
IDR_ANNOUNCE_TEXT¬Not Found
IDC_BUTTON_UNDO¬Found
IDS_STR_CBS2¬Not Found
Why does it have the strange character?
This is due to encoding, you should use set_content CmdLet and you can play on -encoding param if necessary.
$PathArray | % {$_} | Set-Content $Results -Encoding UTF8
I would like to replace a string in a file, and then know if something was actually replaced.
I have many files to parse, and I know only very few will have to be corrected.
So I would like to write the file out only if a changed occurs. Also I would like to be able to trace the changes in a log...
For example, I've been trying this :
(Get-Content $item.Fullname) | Foreach-Object {$_ -replace $old, $new} |
Out-File $item.Fullname
But using I can't tell if any changes were done or not...
Do you have any solution?
Do it in multiple steps:
$content = [System.IO.File]::ReadAllText($item.FullName)
$changedContent = $content -replace $old,$new
if ($content -ne $changedContent) {
# A change was made
# log here
$changedContent | Set-Content $item.FullName
} else {
# No change
}
Use select-string like grep to detect the string and log a message, then use get- and set-content to replace the string:
$item = 'myfile.txt'
$searchstr = "searchstring"
$replacestr = "replacestring"
if (select-string -path $item -pattern $searchstr) {
write-output "found a match for: $searchstr in file: $item"
$oldtext = get-content $item
$newtext = $oldtext.replace($searchstr, $replacestr)
set-content -path $item -value $newtext
}