Powershell: I need help to modify a lengthy SQL script file - tsql

I have a lengthy SQL script which contains MANY/multiple sections like this (amongst other script sections):
USE [NAVDB]
GO
IF NOT EXISTS (SELECT name FROM sys.database_principals
WHERE name = '##Placeholder##')
CREATE USER [MyDomain\adcsuser]
FOR LOGIN [MyDomain\adcsuser]
WITH DEFAULT_SCHEMA = [MyDomain\adcsuser]
GO
GRANT CONNECT TO [MyDomain\adcsuser] AS [dbo]
GO
I need to parse this script tile and modify only the IF NOT EXISTS...CREATE USER... lines of the script such that "##Placeholder##" is replaced by the text within the square brackets [] in the same line immediately following the CREATE USER string.
So, the line in the above snippet would become:
IF NOT EXISTS (SELECT name FROM sys.database_principals
WHERE name = 'MyDomain\adcsuser')
CREATE USER [MyDomain\adcsuser]
FOR LOGIN [MyDomain\adcsuser]
WITH DEFAULT_SCHEMA = [MyDomain\adcsuser]
The file is many hundreds of lines long with many dozen (at least) of these sections in it.
NotePad++ find-replace and macros couldn't handle it because of the "\" in the names between the []s and I couldn't find how to make NP++ copy the text between []s.
Also, I tried reviewing other related answers like: How can I replace every occurrence of a String in a file with PowerShell?, but remain stymied so far.
Because of the complex nature of the script file structure I'm leery of "read the whole file and just use Regex find/replace" approaches, so I was looking for a more... RBAR (Row By Agonizing Row) approach.
Dynamic SQL or parameterized approaches and similar suggestions are NOT APPROPRIATE for this situation as THE SCRIPT WAS ALREADY DYNAMICALLY GENERATED from an existing Production system (and I do not have the source code for the utility which generated this as output). I literally can't make wholesale structural changes like that.
Also, after reading this post once again, I should point out that the whole "IF NOT EXISTS...WITH DEFAULT_SCHEMA [*]" command is on ONE LINE in the script file (if that wasn't sufficiently clear).

For NotePad++ the find and replace support regex.
Example of how to find and replace all lines containing "CREATE USER [someusername]" with your replacement ##Placeholder## would be:
The .* is wildcard, brackets are special characters in regex so to include them as part of the search you have to escape them. So \[.*\] would find anything wrapped in brackets. I just added CREATE USER as an example on finding all those specific lines.
Making sure to select "Regular expression" in Search Mode.
PowerShell, with everything on one line, you can read in each line, find the match, extract the user and then replace; copying that line back out to a new file.
Input test file:
Example PowerShell script:
#create new output file
new-item "C:\temp\test2.txt" -Force
foreach ($line in (get-content "C:\temp\test.txt"))
{
#Find the the user between the brackets and remove the brackets.
$user = ([regex]"\[.*\]").Match($line).value.replace("[","").replace("]","")
#Replace PlaceHolder with user pulled from the brackets and write to new file
$line -replace '##PlaceHolder##', $user | out-file "C:\temp\test2.txt" -Append
}
Then the contents of the output file:

#Tim Mylott,
The entire issue arose from a script that was generated off my production system from Powershell's DBATools' Export-DbaLogin command.
The output script from that tool was like your first text.txt file in that it issues blind CREATE USER commands without first testing to see if the user already existed.
This was inadequate for our needs (our production DB and system have been around for years and now has old, bad 'lint' all over the place in it), so I took the script output into Notepad++ and added the IF NOT EXIST... test logic (see the script in the original question for details) as a prefix to the CREATE USER commands in the script with a global search & replace. Only, I had to put ##Placeholder## in for the username in the SELECT's WHERE clause for the test.
This left me with a script where I had to replace the ##Placeholder## text with the actual username strings in the existing CREATE USER text on the same line in the script file.
The solution, in NP++, which you led me to, was to use Regex in the NP++ Search to select the userid string in the same line. From there, it was fairly easy and straightforward to use a NP++ macro recording to automate the search & replace.
First, I found online a regex to select the text between the first matching pair of []s, which is: (?<=[).+?(?=])
The NP++ script recording was basically this (start Macro recording):
Find:##PlaceHolder## (non-regex search) [NP++ finds the next line to alter]
[Home] (to find the beginning of the line)
Find: (?<=[).+?(?=]) (regex search) [NP++ selects the UserID string in the CREATE USER part of the line)
[Ctrl+C] (copy UserID string to clipboard once Find dialog is closed)
[Home] (to find the beginning of the line again)
Find:##PlaceHolder## (non-regex search) [NP++ finds the ##Placeholder## text in the current line and selects it]
[Ctrl+V] (paste in the UserID string from the clipboard)
(this leaves the cursor on the same location where the corrected command can be visually verified as correct)
In NP++ Use the Play Macro button to manually replay this recorded macro a few times to assure the macro has it right...
Then, use the play multiple times (until the end of file is reached), and Voila!
The script file is all fixed up.
[Note: the DBATools GitHub now has 2 bug reports/requests to add this IF NOT EXISTS logic to the CREATE USER lines in the generated script, so this issue will eventually go away, but for now... there is at least one reasonable solution using NotePad++.]
So, Thank You, Tim, for leading me to a quick answer.
That said, though, your Powershell part of the answer was wrong, so no points there! (it was doing the wrong thing and putting "##Placeholder##" INTO the script, rather than replacing the existing "##PlaceHolder##" string in the script with the actual UserID strings already IN the script.
To repeat, the problem was (logically) to go:
-- FROM THIS:
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser2]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser3]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser5]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser1]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser7]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUser8]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUserA]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUserB]...
IF NOT EXISTS (SELECT ... WHERE name = '##PlaceHolder##') CREATE USER [MyDomain\AUserC]...
-- TO THIS:
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser2') CREATE USER [MyDomain\AUser2]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser3') CREATE USER [MyDomain\AUser3]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser5') CREATE USER [MyDomain\AUser5]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser1') CREATE USER [MyDomain\AUser1]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser7') CREATE USER [MyDomain\AUser7]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUser8') CREATE USER [MyDomain\AUser8]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUserA') CREATE USER [MyDomain\AUserA]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUserB') CREATE USER [MyDomain\AUserB]...
IF NOT EXISTS (SELECT ... WHERE name = 'MyDomain\AUserC') CREATE USER [MyDomain\AUserC]…
Revised Script from Tim's excellent start (version 2) :-)
#create new output file
new-item "C:\temp\test2.sql" -Force
foreach ($line in (get-content "C:\temp\test.sql"))
{
if ($line.Contains("##Placeholder##"))
{
#Find the the user between the brackets and remove the brackets.
$user = ([regex]"\[.+?\]").Match($line).value.replace("[","").replace("]","")
#Replace PlaceHolder with user pulled from the brackets and write to new file
$line -replace '##Placeholder##', $user | out-file "C:\temp\test2.sql" -Append
}
else
{
out-file "C:\temp\test2.sql" -Append -InputObject $line
}
}
...Had to tweak the Regex (to less permissive) and output all the rest of the script lines too.

Related

Removing CR LF improper split line on .txt pipe-delimited flat with Powershell script

Hope all is well! I came across a bit of a tricky issue with a flat file that is exported from Oracle PBCS with some carriage return issues. End users - when inputting data into PBCS - will often press in a specific data field input screen. When the data gets exported representing a specific record with all the data elements representing that data point (intersection) - think like a SQL record - the record element where the user pressed enter causes that record to break at that point - shifting the rest of the data elements in that record to the next line. This is very bad as each record must have the same amount of elements - causing downstream issues in a mapping. In effect one unique record becomes two broken records.
I need a Powershell script that looks at the improper CR LF (Windows system) and reforms each unique record. However, the majority of the records in the flat file are fine so the code will have to be able to discern the "mostly good" from the "very bad" cases.
My flat file is pipe delimited and has a header element. The header element may not need to be considered as I am simply trying to address the fix - a solution could potentially look at the amount of property values for the header record to determine how to format broken records based off a property count using the pipe delimiter - but not sure that is necessary.
I will be honest - there are Jython scripts I tried to no avail - so I felt given that I have employed a couple Powershell scripts for other reasons in the past that I would use this again. I have a basis of a script for a csv file - but this isn't quite working.
$file = Get-Content 'E:\EPM_Cloud\Exports\BUD_PLN\Data\EXPORT_DATA_BUD_PLN.txt'
$file| Foreach-Object {
foreach ($property in $_.PSObject.Properties) {
$property.Value = ($property.Value).replace("`r","").replace("`n","")
}
}
$file|out-file -append 'E:\EPM_Cloud\Exports\BUD_PLN\Data\EXPORT_DATA_BUD_PLN_FINAL.txt'
Here are a few examples of what the before and after case would be if I could get this code to work.
This is supposed to be one record - as you see beginning with "$43K from... the user pressed enter several times. As you see it is pipe delimited - I use the numeric line numbers to show you what I mean since this isn't notepad++. The idea is this should all just be on 1.
Contract TBD|#missing|#missing|#missing|#missing|ORNL to Perform Radio-Chemical (RCA) Measurements|#missing|#missing|#missing|#missing|"$43K from above
$92,903
$14,907
The current $150K to be reprogrammed to XXX, plus another $150K from Fuel Fac for this item to be reprogrammed to RES."|#missing|#missing|#missing|"Summary|X0200_FEEBASED|No_BOC|O-xxxx-B999|xx_xxx_xx_xxx|Plan|Active|FY19|BegBalance"|COMMIT
This is what the output should look like (I have attached screenshots instead). All in 1.
Contract TBD|#missing|#missing|#missing|#missing|ORNL to Perform Radio-Chemical (RCA) Measurements|#missing|#missing|#missing|#missing|"$43K from above $92,903 $14,907 The current $150K to be reprogrammed to XXX, plus another $150K from Fuel Fac for this item to be reprogrammed to RES."|#missing|#missing|#missing|"Summary|X0200_FEEBASED|No_BOC|O-xxxx-B999|xx_xxx_xx_xxx|Plan|Active|FY19|BegBalance"|COMMIT
In other cases the line breaks just once - all defined just by how many times the user presses enter.enter image description here
As you see in the data image - you see how the line splits - this is the point of the powershell. As you see next to that screenshot image - other lines are just fine.
So after checking locally you should be able to just import the file as a csv, then loop through everything and remove CRLF from each property on each record, and output to a new file (or the same, but its safer to output to a new file).
$Records = Import-Csv C:\Path\To\File.csv -Delimiter '|'
$Properties = $Records[0].psobject.properties.name
ForEach($Record in $Records){
ForEach($Property in $Properties){
$Record.$Property = $Record.$Property -replace "[\r\n]"
}
}
$Records | Export-Csv C:\Path\To\NewFile.csv -Delimiter '|' -NoTypeInfo

Powershell text editing

I've got a .bch file that i usually manually edit whenever I need to restore a database. The changes involve removing # from the start of specific lines and changing the database name of the lines where the # is removed to a new name that will be unique every time the script is run.
#DATABASE "YYYYY"
MOVE "YYYYYY"
#TO "H:\MSSQL.1\Data\YYYYY.mdf"
Change to
DATABASE "XXXXX"
MOVE "YYYYYY"
TO "H:\MSSQL.1\Data\XXXXX.mdf"
Would this be possible to do via a script in powershell?
Adding to question as I was pretty vague:
Hi Team, Sorry for the vague question I will get better as I ask more I'm sure. I'm aware of the replace function but in my research I couldn't really find anything to find specific characters. A function involving something like:
"On line Where character 1 from the left = # replace YYYY with XXX"
Then Step two of the shell script could be:
"on line Where string "XXXX" exists delete character 1 from the left"
I just can't find on google if functions like this exist
You haven't given much to go on, but have a start with:
cat somefile.bch | %{$_ -replace "expression","replace"} > newfile.bch

Replace with undefined character in Postgres

I need to do an UPDATE script using the Replace() function of Postgres but I don't know the exact string that I have to replace and I'd like to know if there is some way that I can do this similary the LIKEoperator, using Wildcards.
My problem is that I got a table that contains some scripts and at the end of each one there is a tag <signature> like this:
'SELECT SCRIPT WHATEVER.... < signature>782798e2a92c72b270t920b< signature>'
What I need to do is:
UPDATE table SET script = REPLACE(script,'<signature>%<signature>','<signature>1234ABCDEF567890<signature>')
Whatever the signature is, I need to replace with a new one defined by me. I know using the '%' doesn't work, it was just to ilustrate the effect i want to perform. Is there any way to do this in Postgres 9.5?
with expr
as
(select 'Hello <signature>thisismysig</signature>'::text as
full_text, '<signature>'::text as open,
'</signature>'::text as close
)
select
substring(full_text from
position(open in full_text)+char_length(open)
for
position(close in full_text)- char_length(open)-position(open in full_text)
)
note: with part added for ease of understanding (hopefully).
Use POSIX regex to do the same thing as other answer (but shorter)
select
substring('a bunch of other stuff <signature>mysig</signature>'
from '<signature>(.*?)</signature>')

Can a CSV in a text variable be COPY-ied in PostgreSQL

For example, say I've got the output of:
SELECT
$text$col1, col2, col3
0,my-value,text
7,value2,string
0,also a value,fort
$text$;
Would it be possible to populate a table directly from it with the COPY command?
Sort of. You would have to strip the first two and last lines of your example in order to use the data with COPY. You could do this by using the PROGRAM keyword:
COPY table_name FROM PROGRAM 'sed -e ''1,2d;$d'' inputfile';
Which is direct in that you are doing everything from the COPY command and indirect in that you are setting up an outside program to filter your input.

In SQL, accept a user prompt via keyboard, then add a wildcard to pull everything like it

I can't seem to get this to work. I need to type in a word or letter, and return everything that contains that letter or word at the beginning of it's entry. My code:
ACCEPT v_name PROMPT 'Enter the name or letter: '
select *
from customer c
where UPPER(c.name) LIKE UPPER('%'||$$v_name||'%');
I am using sql developer and sql plus.
If you use &v_name by itself, without a prompt, the tool will automatically prompt you. You only need ACCEPT and PROMPT if you want to customize the message to the user.
Put the variable inside the quotes when using its value in a string, prefixed by &. It is a substitution var, so it doesn't need concatenation. Substitution vars are just interpolated variables implemented in the query tool, prior to the SQL being parsed, which explains why they work inside quotes.
select * from customer c where UPPER(c.name) LIKE UPPER('%&v_name%');
Or customizing the prompt, using accept. Note no & on the v_name in the ACCEPT command, only in the substitution:
ACCEPT v_name PROMPT 'Enter the name or letter: '
select * from customer c where UPPER(c.name) LIKE UPPER('%&v_name%');