2

I am doing MySql queries pipeline and using results to transfer data do storage account.

For DB query and transfer data to .txt file, I use this command:

sqlcmd -S $(Server_prod) -i "G:\DB_Automation\xxx\Night_Batch_Report_4.sql" -o "G:\DB_Automation\SQL_Queries\xxx\4th_init.txt"

Later I use that script to transfer all that was inside .txt file to .xlsx format.

- task: PowerShell@2
                  displayName: Tranfer 4st night job --> Excel
                  inputs:
                    targetType: 'inline'
                    script: |
                      $rawData = Get-Content -Path 'G:\DB_Automation\SQL_Queries\Results\Night_batch\1st_init.txt' | Where-Object {$_ -match '\S'}
                      $delimiter = [cultureinfo]::CurrentCulture.TextInfo.ListSeparator 
                      ($rawData -replace '\s+' , $delimiter) | Set-Content -Path 'G:\DB_Automation\SQL_Queries\Results\Night_batch\theNewFile.csv'
                      Import-Csv -Path 'G:\DB_Automation\SQL_Queries\Results\Night_batch\theNewFile.csv' | Export-Excel -Path 'G:\DB_Automation\SQL_Queries\Results\Night_batch\batch_audit_report_$(Build.BuildNumber)_prod_Night.xlsx' -Autosize -WorkSheetname '04-Check all task completed'

The issue with this approach is that I can see many more content to be transferred in .txt file than later lands in .xlsx.

Please refer to this screenshot:

enter image description here

enter image description here

Some columns are not transferred.

Can you please tell me if it's there an issue with PowerShell script / formatting?

Thanks

UPDATE:

I have modified it to be now:

              $text = Get-Content -Raw -Path 'G:\DB_Automation\xxx\Night_batch\8th_init.txt' 
              $rawData = ($text -split "\(\d* rows affected\)")[1].Trim() -split "\n"
              $delimiter = [cultureinfo]::CurrentCulture.TextInfo.ListSeparator 
              $data = ($rawData -replace '\s+' , $delimiter) | ConvertFrom-Csv
              $data | Export-Excel -Path 'G:\DB_Automation\xxx\Night_batch\batch_audit_report_$(Build.BuildNumber)_prod_Night.xlsx' -Autosize -WorkSheetname '08-t_team_role_conn' 

But now....

For some queries it works fine (see screenshot):

enter image description here

but for some I get empty Excel sheet, despite queries are done well.

enter image description here

enter image description here

Can you shed some light on this issue?

Releated Issue

All works fine, except this: if you take a look on the cell you will find date separated from time.

In that case script would not pick whole cell but will divide content to date and time and will push it as new columns.

Is there a way to get only particular content of a cell instead of dividing it?

enter image description here

3
  • As an aside: If you use [cultureinfo]::CurrentCulture.TextInfo.ListSeparator as the separator to generate your CSV data, you must use -UseCulture with Import-Csv, because Import-Csv (and also Export-Csv) by default uses , regardless of what the current culture is. Commented Feb 13, 2024 at 17:37
  • 1
    Even though you mention MySQL, the use of sqlcmd suggests MSSQL (Microsoft SQL Server). With the latter, you can avoid the need for text parsing if you use Invoke-SqlCmd ... | Export-Excel ... instead - see this answer. Commented Feb 13, 2024 at 17:41
  • Also note that your screenshot shows that your query returned no rows at all. Commented Feb 13, 2024 at 20:34

1 Answer 1

2

Problem

The problem is your attempt to extract a *.csv file from the raw data in your first screenshot isn't working properly.

If you start with this:

$lines = @"
Changed database context to 'GPF_PROD_DB'

(0 rows affected)
entry_time batch_run_num batch_name batch_id task_name task_id primary_key chunk_id
---------- ------------- ---------- -------- --------- ------- ----------- --------
       aaa           bbb        ccc      ddd       eee     fff         ggg      hhh

(1 rows affected)
"@ -split "`n"

and your code:

$rawData = $lines | Where-Object {$_ -match '\S'}
$delimiter = [cultureinfo]::CurrentCulture.TextInfo.ListSeparator 
($rawData -replace '\s+' , $delimiter)

you get:

Changed,database,context,to,'GPF_PROD_DB'
(0,rows,affected)
entry_time,batch_run_num,batch_name,batch_id,task_name,task_id,primary_key,chunk_id
----------,-------------,----------,--------,---------,-------,-----------,--------
aaa,bbb,ccc,ddd,eee,fff,ggg,hhh
(1,rows,affected)

and when you read that back in with Import-Csv it takes column headers from the first row - there's only 5 columns so it discards the data in any additional columns (i.e task_id onwards).

Exporting this data to excel (or even back to a CSV with Export-Csv will only write the data from the first 5 columns since that's all that is being imported.

Workaround

You need to extract just the tabular data rows from your source text file.

I don't know how fragile this approach would be, but you could split on the (x rows affected) and take the second result from the text file - e.g.:

# use "Get-Content -Raw" to read the file as a single string in your code
$text = @"
Changed database context to 'GPF_PROD_DB'

(0 rows affected)
entry_time batch_run_num batch_name batch_id task_name task_id primary_key chunk_id
---------- ------------- ---------- -------- --------- ------- ----------- --------
       aaa           bbb        ccc      ddd       eee     fff         ggg      hhh

(1 rows affected)
"@

$rawData = ($text -split "\(\d* rows affected\)")[1].Trim() -split "\n" `
    | foreach-object -begin { $i = 0 } -process { if( $i -ne 1 ) { $_ } $i++ } `
    | foreach-object { $_.Trim() }

What this does is:

  • ($text -split "\(\d* rows affected\)") - split the entire text file into chunks based on separators in the format "(x rows affected)"
  • [1] - take the second chunk - i.e. the one that contains the tabular data (the array is zero-indexed, so 1 is the second item in the array)
  • .Trim() - remove leading and trailing whitespace including line breaks
  • -split "\n" - split the chunk into lines
  • foreach-object -begin { $i = 0 } -process { if( $i -ne 1 ) { $_ } $i++ } - filters the lines of text in the selected chunk and removes the second one (that contains the ------- column header underlines)
  • foreach-object { $_.Trim() } - finally trims the remaining lines of text

The result is equivalent to :

$rawData = @(
    "entry_time batch_run_num batch_name batch_id task_name task_id primary_key chunk_id",
    "aaa           bbb        ccc      ddd       eee     fff         ggg      hhh"
)

and now you can process it like you already were:

$delimiter = [cultureinfo]::CurrentCulture.TextInfo.ListSeparator

# no need to write to a file and re-import - just use ConvertFrom-Csv
$data = ($rawData -replace '\s+', $delimiter) | ConvertFrom-Csv

$data | Export-Excel -Path ...

The result should look like this in Excel:

A B C D E F G H
1 entry_time batch_run_num batch_name batch_id task_name task_id primary_key chunk_id
2 aaa bbb ccc ddd eee fff ggg hhh

This might break if your source file format changes, but it'll hopefully point you in the right direction...


Notes

  • Updated to discard the line of ------ header underlines in the output from sqlcmd - thanks @mklement0!
Sign up to request clarification or add additional context in comments.

12 Comments

+1 for the explanation and the fundamentals of the approach, but note that sqlcmd's output also has a separator line below the header line. The question mentions MySQL, but the use of sqlcmd suggests MSSQL. With the latter, you can avoid the need for text parsing if you use Invoke-SqlCmd ... | Export-Excel ... - see this answer. Otherwise, sqlcmd.exe output could be optimized for easier parsing / direct CSV output (with limitations); see the other answers to the linked post, notably this one.
@mklement0 - I've updated to exclude the row of underscores - thanks for the tip :-). Like you say, there are probably better ways to access the data if the OP can alter the upstream process to use Invoke-SqlCmd instead, but this answer will hopefully help if they're limited to only processing the log file...
@mklement0 - I do usually use ### headings, but subconsciously decided to reduce the wear on my # key today so only typed ##. I just made the headings smaller by putting another one in with ctrl-c \ ctrl-v though :-).
@MichałPicheta - another option is to look for different logic to parse the tabular data - from memory I think mklement0 has answered a question in the distant past about how to do this by taking the underscores as column width markers and breaking lines on those boundaries...
@mclayton, in fact, we both did: stackoverflow.com/a/66784512/45375
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.