Powershell script to write SQL results to xlsx - Excel sheet is blank even though the query returns records - sql-server

We're writing the following Powershell script to run a simple SELECT statement against our SQL Server database and dump the results into an Excel file. The query returns 4 records but the resulting Excel file is blank. I can't determine why this would be.
This is what I have so far:
$FileName = "C:\Users\Me\Documents\TestFile.xlsx";
$ConnectionString = "Our DB connection string"
$SqlQuery = #"
SELECT Id
FROM dbo.Orders
WHERE OrderExpiry IS NULL
AND ReturnPeriod IS NULL
AND Returned IS NULL
"#;
#Connect to SQL Server using non-SMO class System.Data
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = $ConnectionString;
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $SqlQuery;
$SqlCmd.Connection = $SqlConnection;
$SqlCmd.CommandTimeout = 60
## – Extract and build the SQL data object ‘$DataSetTable’:
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$DataSetTable = $DataSet.Tables["Table"];
## – Create an Excel Application instance:
$xlsObj = New-Object -ComObject Excel.Application;
## – Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0;
$xlsWb = $xlsobj.Workbooks.Add();
$xlsSh = $xlsWb.Worksheets.item(1);
## – Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## – Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
};
## – Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
## – Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## – Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = `
$rec.$($Coln.ColumnName).ToString();
$ColData++;
};
$rowData++; $ColData = 1;
};
## – Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange;
$xlsRng.EntireColumn.AutoFit();
## – Saving Excel file – if the file exist do delete then save
$xlsFile = $FileName;
if (Test-Path $xlsFile)
{
Remove-Item $xlsFile
$xlsObj.ActiveWorkbook.SaveAs($xlsFile);
}
else
{
$xlsObj.ActiveWorkbook.SaveAs($xlsFile);
};
## Quit Excel and Terminate Excel Application process:
$xlsObj.Quit(); (Get-Process Excel*) | foreach ($_) { $_.kill() };
$SqlConnection.Close()
$Attachment = $xlsFile
Upon executing the script, once the query runs successfully, the script outputs 4 (which is the number of records this query returns) and also outputs True, and the Excel file gets successfully created. But it's blank. Any ideas why I'm getting blank output?

Related

Sqlbulkcopy Excessive Memory Consumtion even with EnableStreaming and low BatchSize

I try to bulk load data from Oracle to SqlServer through Powershell Sqlserver Module Sqlbulkcopy
On small Data, everything works fine, but on big Datasets, even if bachsize and streaming are set, sqlbulkcopy is taking all the memory available... until an out of memory
Also the notify function seems to give no answer, so I guess even with streaming=True, the process first load everything to memory...
What did I missed ?
$current = Get-Date
#copy table from Oracle table to SQL Server table
add-type -path "D:\oracle\product\12.1.0\client_1\odp.net\managed\common\Oracle.ManagedDataAccess.dll";
#define oracle connectin string
$conn_str = "cstr"
# query for oracle table
$qry = "
SELECT
ID,CREATEDT,MODIFIEDDT
FROM MYTABLE
WHERE source.ISSYNTHETIC=0 AND source.VALIDFROM >= TO_Date('2019-01-01','yyyy-mm-dd')
";
# key (on the left side) is the source column while value (on the right side) is the target column
[hashtable] $mapping = #{'ID'='ID';'CREATEDT'='CREATEDT';'MODIFIEDDT'};
$adapter = new-object Oracle.ManagedDataAccess.Client.OracleDataAdapter($qry, $conn_str);
#$info = new-object Oracle.ManagedDataAccess.Client;
#Write-Host ( $info | Format-Table | Out-String)
$dtbl = new-object System.Data.DataTable('MYTABLE');
#this Fill method will populate the $dtbl with the query $qry result
$adapter.Fill($dtbl);
#define sql server target instance
$sqlconn = "cstr";
$sqlbc = new-object system.data.sqlclient.Sqlbulkcopy($sqlconn)
$sqlbc.BatchSize = 1000;
$sqlbc.EnableStreaming = $true;
$sqlbc.NotifyAfter = 1000;
$sqlbc.DestinationTableName="DWHODS.MYTABLE";
#need to tell $sqlbc the column mapping info
foreach ($k in $mapping.keys)
{
$colMapping = new-object System.Data.SqlClient.SqlBulkCopyColumnMapping($k, $mapping[$k]);
$sqlbc.ColumnMappings.Add($colMapping) | out-null
}
$sqlbc.WriteToServer($dtbl);
$sqlbc.close;
$end= Get-Date
$diff= New-TimeSpan -Start $current -End $end
Write-Output "import needed : $diff"
Thanks to Jeroen, I changed the code like this, now its no more consuming memory :
$oraConn = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($conn_str);
$oraConn.Open();
$command = $oraConn.CreateCommand();
$command.CommandText=$qry;
$reader = $command.ExecuteReader()
...
$sqlbc.WriteToServer($reader);

Trying to Import an XML file into a MS SQL table using PowerShell

I am trying to import an XML file into a MS SQL table using PowerShell, I have modified a PowerShell script I already had that worked fine but could do with some help as the new XML file has multiple entries for some elements.( I have included a sample of the XML file)
Table layout is below and as is a sample of the file I am trying to import and also my script I am trying to modify to get working.
Table layout is:
------------------------------
:supplier_id : name : Symbol :
and here is an part of the XML file itself
<SupplierMapping supplier_id="4536" name="Joby">
<Symbol>Joby</Symbol>
</SupplierMapping>
<SupplierMapping supplier_id="4537" name="ACT">
<Symbol>ACT</Symbol>
<Symbol>ADVANCED CABLE TECH</Symbol>
<Symbol>ADVANCED CABLE TECHNOLOGY</Symbol>
<Symbol>IEC LOCK</Symbol>
</SupplierMapping>
As you can see some supplier id's and names will have multiple <Symbol> names so I would like to have multiple entries in my table for those, however the script I have modified only seems to pull in the supplier_id and name elements and misses the <Symbol> part entirely. any help or guidance would be much appreciated.
Set-ExecutionPolicy Unrestricted -Scope LocalMachine
[String]$global:connectionString = "Data Source=Apps2\Apps2;Initial Catalog=DTEDATA;Integrated Security=SSPI";
[System.Data.DataTable]$global:dt = New-Object System.Data.DataTable;
[System.Xml.XmlTextReader]$global:xmlReader = New-Object System.Xml.XmlTextReader("C:\Scripts\icecat\supplier_mapping.xml");
[Int32]$global:batchSize = 100;
function Add-FileRow() {
$newRow = $dt.NewRow();
$null = $dt.Rows.Add($newRow);
$newRow["supplier_id"] = $global:xmlReader.GetAttribute("supplier_id");
$newRow["name"] = $global:xmlReader.GetAttribute("name");
$newRow["Symbol"] = $global:xmlReader.GetAttribute("Symbol");
}
# init data table schema
$da = New-Object System.Data.SqlClient.SqlDataAdapter("SELECT * FROM Supplier_Mapping_Test WHERE 0 = 1", $global:connectionString);
$null = $da.Fill($global:dt);
$bcp = New-Object System.Data.SqlClient.SqlBulkCopy($global:connectionString);
$bcp.DestinationTableName = "dbo.Supplier_Mapping_Test";
$recordCount = 0;
while ($xmlReader.Read() -eq $true) {
if (($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element) -and ($xmlReader.Name -eq "SupplierMapping"))
Add-FileRow -xmlReader $xmlReader;
$recordCount += 1;
if (($recordCount % $global:batchSize) -eq 0) {
$bcp.WriteToServer($dt);
$dt.Rows.Clear();
Write-Host "$recordCount file elements processed so far";
}
}
}
if ($dt.Rows.Count -gt 0) {
$bcp.WriteToServer($dt);
}
$bcp.Close();
$xmlReader.Close();
Write-Host "$recordCount file elements imported ";
catch {
throw;
}

PowerShell pipe / SQL insert query data limits (and increasing them?)

Are there any limits to the size of a string you can assign to a variable in powershell or any limits to the size of the text sent within an SQL INSERT query?
I have a big CSV file coming in to PowerShell and through string construction in a foreach loop I am generating SQL INSERT queries for each row. The resulting INSERT query; INSERT query; is over about 4MB.
The SQL server has a perfect schema to receive the data, however, when sending the 4MB collection of INSERT queries (each seperated by ;) I get an error that looks to me like the long 4MB set of insert queries was truncated somehow. I guess I have hit some kind of limit.
Is there a way of getting around this (programatically in PowerShell) or a way of increasing the size limit of an acceptable collection of SQL INSERT queries?
My code is using System.Data.SqlClient.SqlConnection and System.Data.sqlclient.SqlCommand.
Smaller datasets work ok but the larger datasets give an error like the following example. Each different dataset gives off a different "Incorrect syntax near" indicator.
Exception calling "ExecuteNonQuery" with "0" argument(s): "Incorrect syntax
near '('."
At C:\Users\stuart\Desktop\git\ADStfL\WorkInProgress.ps1:211 char:3
+ $SQLCommand.executenonquery()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SqlException
In my experience, the best performing way to do this is to load the CSV into a DataTable and then use SQLBulkCopy.
$ErrorActionPreference = 'Stop';
$Csv = Import-Csv -Path $FileName;
$SqlServer = 'MyServer';
$SqlDatabase = 'MyDatabase';
$DestinationTableName = 'MyTable';
# Create Connection String
$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;
# Define your DataTable. The column order of the DataTable must either match the table in the database, or
# you must specify the column mapping in SqlBulkCopy.ColumnMapping. If you have an IDENTITY column, it's a
# bit more complicated
$DataTable = New-Object -TypeName System.Data.DataTable -ArgumentList $DestinationTableName;
$NewColumn = $DataTable.Columns.Add('Id',[System.Int32]);
$NewColumn.AllowDBNull = $false;
$NewColumn = $DataTable.Columns.Add('IntegerField',[System.Int32]);
$NewColumn.AllowDBNull = $false;
$NewColumn = $DataTable.Columns.Add('DecimalField',[System.Decimal]);
$NewColumn.AllowDBNull = $false;
$NewColumn = $DataTable.Columns.Add('VarCharField',[System.String]);
$NewColumn.MaxLength = 50;
$NewColumn = $DataTable.Columns.Add('DateTimeField',[System.DateTime]);
$NewColumn.AllowDBNull = $false;
# Populate your datatable from the CSV file
# You may find that you need to type cast some of the fields.
$Csv | ForEach-Object {
$NewRow = $DataTable.NewRow();
$NewRow['Id'] = $_.Id;
$NewRow['IntegerField'] = $_.IntegerField;
$NewRow['DecimalField'] = $_.DecimalFiled;
$NewRow['StringField'] = $_.StringField1;
$NewRow['DateTimeField'] = $_.DateTimeField1;
$DataTable.Rows.Add($NewRow);
}
# Create Connection
$SqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
# Open Connection
$SqlConnection.Open();
# Start Transaction
$SqlTransaction = $SqlConnection.BeginTransaction();
# Double check the possible options at https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopyoptions(v=vs.110).aspx
# If you need multiple then -bor them together
$SqlBulkCopyOptions = [System.Data.SqlClient.SqlBulkCopyOptions]::CheckConstraints;
# Create SqlBulkCopy class
$SqlBulkCopy = New-Object -TypeName System.Data.SqlClient.SqlBulkCopy -ArgumentList $SqlConnection, $SqlBulkCopyOptions, $SqlTransaction;
# Specify destination table
$SqlBulkCopy.DestinationTableName = $DestinationTableName;
# Do the insert; rollback on error
try {
$SqlBulkCopy.WriteToServer($DataTable);
$SqlTransaction.Commit();
}
catch {
# Roll back transaction and rethrow error
$SqlTransaction.Rollback();
throw ($_);
}
finally {
$SqlConnection.Close();
$SqlConnection.Dispose();
}
The other method is to use an SQLCommand and do it row by row:
$ErrorActionPreference = 'Stop';
$Csv = Import-Csv -Path $FileName;
$SqlServer = 'MyServer';
$SqlDatabase = 'MyDatabase';
# Create Connection String
$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;
# Create Connection
$SqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
# Create Command
$InsertCommandText = 'INSERT INTO DestinationTable (Id, IntegerField, DecimalField, StringField, DateTimeField) VALUES (#Id, #IntegerField, #DecimalField, #StringField, #DateTimeField)';
$InsertCommand = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $SqlConnection;
[void]$InsertCommand.Parameters.Add('#Id', [System.Data.SqlDbType]::Int);
[void]$InsertCommand.Parameters.Add('#IntegerField', [System.Data.SqlDbType]::Int);
[void]$InsertCommand.Parameters.Add('#DecimalField', [System.Data.SqlDbType]::Decimal);
[void]$InsertCommand.Parameters.Add('#StringField', [System.Data.SqlDbType]::VarChar,50);
[void]$InsertCommand.Parameters.Add('#DateTimeField', [System.Data.SqlDbType]::DateTime);
# Open connection and start transaction
$SqlConnection.Open()
$SqlTransaction = $SqlConnection.BeginTransaction();
$InsertCommand.Transaction = $SqlTransaction;
$RowsInserted = 0;
try {
$line = 0;
$Csv | ForEach-Object {
$line++;
# Specify parameter values
$InsertCommand.Parameters['#Id'].Value = $_.Id;
$InsertCommand.Parameters['#IntegerField'].Value = $_.IntegerField;
$InsertCommand.Parameters['#DecimalField'].Value = $_.DecimalField;
$InsertCommand.Parameters['#StringField'].Value = $_.StringField;
$InsertCommand.Parameters['#DateTimeField'].Value = $_.DateTimeField;
$RowsInserted += $InsertCommand.ExecuteNonQuery();
# Clear parameter values
$InsertCommand.Parameters | ForEach-Object { $_.Value = $null };
}
$SqlTransaction.Commit();
Write-Output "Rows affected: $RowsInserted";
}
catch {
# Roll back transaction and rethrow error
$SqlTransaction.Rollback();
Write-Error "Error on line $line" -ErrorAction Continue;
throw ($_);
}
finally {
$SqlConnection.Close();
$SqlConnection.Dispose();
}
Edit: Oh, I forgot one important point. If you need to set the value of a field to null in the database, you need to set it's value to [System.DBNull]::Value, not $null.

Powershell: Excel combine worksheets into a single worksheet

I have some web script that I've adapted to run 7 T-SQL queries and output the results into 1 Excel workbook, one worksheet per query. I've just been asked if I can combine all 7 worksheets into one.
Here's my sample code which does copy a worksheet, however the entire column(s) are selected instead of just the UsedData. Also, the first worksheet's data on the destination worksheet is replaced by the second worksheets data.
Questions: Would it be simpler to get Powershell to output the 7 queries into One Excel Worksheet separated by two blank rows? Or modify the existing Powershell script to create the 7 worksheets then combine them into one?
Code is not pretty! I also have been really lost using $Excel = New-Object -ComObject excel.application followed by $Excel | Get-Member to explore how to get PowerShell to work with Excel. References on MSDN are usually for VB or C languages and I can't translate that into PowerShell.
--Edit, add code that stores 7 Query results in an array and outputs to the console. The data is correct but I'm just unsure how to approach piping that data into a single Excel Worksheet.
$docs = "C:\Temp\SQL\test.xlsx"
If (Test-Path $docs){Remove-Item $docs}
Function First-Query {
param([string[]]$queries)
$xlsObj = New-Object -ComObject Excel.Application
$xlsObj.DisplayAlerts = $false
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0
$xlsWb = $xlsobj.Workbooks.Add(1)
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Coun))
$xlsSh.Name = 'Test'
for ($i = 0; $i -lt $queries.Count; $i++){
$query = $queries[$i]
$SQLServer = 'Server'
$Database = 'DataBase'
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $query
$SqlCmd.Connection = $SqlConnection
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd;
$tables = New-Object System.Data.DataSet;
$SqlAdapter.Fill($tables)
$TableArray = #($tables)
$SqlConnection.Close()
$DataSetTable = $TableArray.Tables[0]
}#End For Loop
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames){
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
}
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows){
foreach ($Coln in $getColumnNames){
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = `
$rec.$($Coln.ColumnName).ToString()
$ColData++
}
$rowData++; $ColData = 1
}
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange
$xlsRng.EntireColumn.AutoFit() | Out-Null
#End for loop.
#Delete unwanted Sheet1.
$xlsWb.Sheets.Item('Sheet1').Delete()
#Set Monday to Active Sheet upon opening Workbook.
$xlsWb.Sheets.Item('Monday').Activate()
## ---------- Saving file and Terminating Excel Application ---------- ##
$xlsFile = "C:\Temp\SQL\test.xlsx"
$xlsObj.ActiveWorkbook.SaveAs($xlsFile) | Out-Null
$xlsObj.Quit()
## - End of Script - ##
start-sleep 2
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsRng)) {'cleanup xlsRng'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsSh)) {'cleanup xlsSh'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWb)) {'cleanup xlsWb'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)) {'cleanup xlsObj'}
[gc]::collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null
}#End Function
$queries = #()
$queries += #'
SELECT DISTINCT
'#
First-Query -queries $queries
Not sure I really understand what your problem is but below is a "template" that might help you to do what you want. It shows you how you can create sheets and handle them. You'll have to fill the blanks (see the commented section, where you have to call your query function).
param (
[string] $ExcelFile = (Read-Host "Enter full path for Excel file")
)
try
{
$Error.Clear()
# http://support.microsoft.com/kb/320369
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo] "en-US"
Push-Location
$scriptPath = Split-Path -parent $MyInvocation.MyCommand.Path
Set-Location $scriptPath
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $True
$WorksheetCount = 7
$Workbook = $Excel.Workbooks.Add()
$Workbook.Title = 'My Workbook'
$weekdays = #("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")
($WorksheetCount - 1)..0 | %{
$sheet = $Excel.Worksheets.Add()
$sheet.Name = $weekdays[$_]
# $dataTable = Execute-Your-Query-Here-Returning-A-Data-Table
# $x = 0
# $dataTable | %{
# $sheet.cells.item($x, 1) = ...
# $sheet.cells.item($x, 2) = ...
# $x++
# }
}
$excel.ActiveWorkbook.SaveAs("$ExcelFile")
}
catch
{
"$($MyInvocation.InvocationName): $Error"
}
finally
{
Pop-Location
$Excel.Quit()
$Excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}

Powershell function that accepts multidimensional arrays as parameters

I'm trying to take a web script and make it a function that accepts an array of strings as arguments. Currently I work around this by making two functions from the same script. Then I use My-Function1 -arr $1 -arr2 $2 and after that use My-Function2 -arr $1 -arr2 $2 to get around the array problem.
There has to be a much cleaner method for passing this type of data to only one function but in my searching I have not see anything. I also saw a question on using the Tables refresh method to run a new query but with my limited scripting experience, I wasn't sure how to use that with my script.
The reason for two functions is: 1) First query runs and the results are inserted into a New Excel WorkBook and Worksheet. 2) Next the 2nd function is called to run the same T-SQL query however this time, Open workbook from #1 then insert a new named worksheet.
Original script from http://www.maxtblog.com/2014/06/powershell-extracting-sql-server-data-into-excel/
$docs = "D:\Scripts\Reboots2.xlsx"
If (Test-Path $docs){Remove-Item "D:\Scripts\Reboots2.xlsx"} Else {Continue}
Function First-Query {
param([string[]]$arr,$arr2)
### SQL query results sent to Excel
$SQLServer = 'SERVERNAME'
$Database = 'DATABASENAME'
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True";
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $arr;
$SqlCmd.Connection = $SqlConnection;
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$SqlConnection.Close()
$DataSetTable = $DataSet.Tables["Table"];
## - Create an Excel Application instance:
$xlsObj = New-Object -ComObject Excel.Application;
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0;
$xlsWb = $xlsobj.Workbooks.Add(1);
$xlsSh = $xlsWb.Worksheets.item(1);
$xlsSh.Name = $($arr2)
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
};
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = $rec.$($Coln.ColumnName).ToString();
$ColData++;
};
$rowData++; $ColData = 1;
};
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange;
$xlsRng.EntireColumn.AutoFit() | Out-Null
## ---------- Saving file and Terminating Excel Application ---------- ##
$xlsFile = "D:\Scripts\Reboots.xlsx"
$xlsObj.ActiveWorkbook.SaveAs($xlsFile) | Out-Null
$xlsObj.Quit()
## - End of Script - ##
While([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)){Remove-Variable xlsObj}
start-sleep 1
}#End Function
Function Rest-Query {
param([string[]]$arr,$arr2)
### SQL query results sent to Excel
$SQLServer = 'SERVERNAME'
$Database = 'DATABASENAME'
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True";
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $arr;
$SqlCmd.Connection = $SqlConnection;
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$SqlConnection.Close()
$DataSetTable = $DataSet.Tables["Table"];
## - Create an Excel Application instance:
$xlsObj = New-Object -ComObject Excel.Application;
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0;
$xlsWb = $xlsObj.Workbooks.Open("D:\Scripts\Reboots.xlsx")
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Count))
$xlsSh.Name = $($arr2)
$xlsObj.DisplayAlerts = $false
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
};
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = $rec.$($Coln.ColumnName).ToString();
$ColData++;
};
$rowData++; $ColData = 1;
};
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange;
$xlsRng.EntireColumn.AutoFit() | Out-Null
## ---------- Saving file and Terminating Excel Application ---------- ##
$xlsFile = "D:\Scripts\Reboots.xlsx"
$xlsObj.ActiveWorkbook.SaveAs($xlsFile) | Out-Null
$xlsObj.Quit()
$xlsObj.DisplayAlerts = $true
While([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)){Remove-Variable xlsObj}
[gc]::collect()
[gc]::WaitForPendingFinalizers()
start-sleep 1
}#End Function2
$a = #'
SELECT DISTINCT
'#
$b = #'
SELECT DISTINCT
'#
$c = #'
SELECT DISTINCT
'#
$d = #'
SELECT DISTINCT
'#
$e = #'
SELECT DISTINCT
'#
$f = #'
SELECT DISTINCT
'#
$g = #'
SELECT DISTINCT
'#
First-Query -arr $a -arr2 Monday
Rest-Query -arr $b -arr2 Tuesday
Rest-Query -arr $c -arr2 Wednesday
Rest-Query -arr $d -arr2 Thursday
Rest-Query -arr $e -arr2 Friday
Rest-Query -arr $f -arr2 Saturday
Rest-Query -arr $g -arr2 Sunday
You can indeed write a single function for what you wanted to do, however you need to rearrange your code and add some structure to it.
I have tried to give some structure to the example below which you will need to adapt to your script.
Basically, and from what I could understand from the code you posted, you are running a specific SQL query for each week day and want to save the results in Excel.
In the example below I'm using a for loop to ensure the index used in each array is the same.
In the comments are the commands you need to add from your code.
Function First-Query {
param([string[]]$arr,[string[]]$arr2)
#initialize Excel
# do your Excel commands like opening the file, adding the workbook
#Now perform the query by using the appropriate element in each array (1 query / day)
for ($i = 0; $i -lt $arr.Count; $i++)
{
$day = $arr2[$i]
$query = $arr[$i]
# then add the worksheet
$xlsSh.Name = $day
# run the query
$SqlCmd.CommandText = $query;
# Save the results in the Excel columns
}
# Save and Quit
}#End Function
$arr = #()
$arr += "SELECT DISTINCT"
$arr += "ANOTHER QUERY SELECT DISTINCT"
# additional queries added with $arr +=
$weekdays = #("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")
First-Query -arr $arr -arr2 $weekdays

Resources