Using powershell to handle multiple SQL resultsets - sql-server

I have several queries that I use for identifying issues in a SQL database but I'm trying to create at powershell script that I can use to do this automatically. The trouble I am having is that when I invoke my SQL scripts there are multiple result sets and my script only seems to capture the first set. I'm wondering what I need to do to cycle through all the results. This is the code with just some simple selects
$dataSource = 'Server'
$database = "DB"
$sqlcommand = #"
Select TOP 1000 * from tblA;
Select TOP 1000 * from tblB
"#
Function Convert-Dataset
{
Param
(
[Parameter(Mandatory=$true)]
$dataset
)
Begin
{
$return=#()
For($r = 0; $r -lt $dataset.tables[0].rows.Count; $r++)
{
$table= new-object psobject
If($dataset.tables[0].columns.ColumnName.Count -le 1)
{
$colName = [String]$dataset.tables[0].columns.ColumnName
If($dataset.tables[0].rows.Count -eq 1)
{
$colValue = [string]$dataset.tables[0].rows.$colName
}
Else
{
$colValue = [string]$dataset.tables[0].rows[$r].$colName
}
$table | Add-Member -memberType noteproperty -Name $colName -Value $colValue
}
Else{
For($c = 0; $c -lt $dataset.tables[0].columns.ColumnName.Count; $c++)
{
$colName = [String]$dataset.tables[0].columns.ColumnName[$c]
$colValue = [string]$dataset.tables[0].rows[$r][$c]
$table | Add-Member -memberType noteproperty -Name $colName -Value $colValue
}
}
$return +=$table
}
}
End
{
Return $return
}
}
$connectionString = "Data Source=$dataSource; " +
"Integrated Security=True; " +
"Initial Catalog=$database"
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
$connection.Open()
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$return=Convert-Dataset -dataset $dataset
$return | Out-GridView

I figured it out
$connectionString = "Data Source=$dataSource; " +
"Integrated Security=True; " +
"Initial Catalog=$database"
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
$connection.Open()
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
ForEach($table in $dataset.Tables)
{
$table |Out-GridView -PassThru
}

Related

how to get column records into a list

I have 2 PowerShell scripts I wanna combine I have this one for doing data dumps
$query = "use [ISTABLocalDB]
SELECT
Item.[ID] as PartIdDB
,Item.[ItemNumber] as Varenummer
,ImageFile.[ResourceFile_ID] as ImageID
,ImageFile.[Description] as ImageName
, CASE WHEN ImageFile.[ResourceFile_ID] is null
THEN ''
ELSE
CONCAT('F:\App\ISTAB.Data\pictures_global\P\',
SUBSTRING(CONVERT(varchar, Item.[ID]), 1, 3), '\',
SUBSTRING(CONVERT(varchar, Item.[ID]), 4, 3), '\',
SUBSTRING(CONVERT(varchar, Item.[ID]), 7, 3), '\',
ImageFile.[ResourceFile_ID],'-g')
END as PathOnDrive
,Item.[ItemType_ID]"
$extractFile = "$path $date.csv"
$connectionTemplate = "Data Source={0};Integrated Security=SSPI;Initial Catalog={1};"
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$command = New-Object System.Data.SqlClient.SqlCommand
$command.CommandText = $query
$command.Connection = $connection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $command
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$connection.Close()
$DataSet.Tables[0] | Export-Csv $extractFile -NoTypeInformation
and this one for copy pictures where I manually pasting in the path which I get from PathOnDrive in the query into the $imagesList
$targetFolderName = "C:\test"
$sourceFolderName = "F:\App\ISTAB.Data\pictures_global\P"
$imagesList = (
"F:\App\ISTAB.Data\pictures\P\122\338\7\1326647",
"F:\App\ISTAB.Data\pictures\P\179\924\0\1678117"
)
foreach ($itemToCopy in $imagesList)
{
$targetPathAndFile = $itemToCopy.Replace( $sourceFolderName , $targetFolderName )
$targetfolder = Split-Path $targetPathAndFile -Parent
if (!(Test-Path $targetfolder -PathType Container)) {
New-Item -Path $targetfolder -ItemType Directory -Force
}
Copy-Item -Path $itemToCopy -Destination $targetPathAndFile
}
so my question is how do I get the all the records from the PathOnDrive column into my $imagesList automatically
i figured it out
foreach ($Columns in $DataSet.Tables[0].Rows) {
$imagesList = "$($Columns.PathOnDrive)"
write-Output "$($imagesList)"
$targetFolderName = "D:\DataFeed\Pictures\Parts"
$sourceFolderName = "F:\App\ISTAB.Data\pictures_global\P"
foreach ($itemToCopy in $imagesList)
{
$targetPathAndFile = $itemToCopy.Replace( $sourceFolderName , $targetFolderName )
$targetfolder = Split-Path $targetPathAndFile -Parent
if (!(Test-Path $targetfolder -PathType Container)) {
New-Item -Path $targetfolder -ItemType Directory -Force
}
Copy-Item -Path $itemToCopy -Destination $targetPathAndFile
}
}

How can handle principal/mirror status in for-each and switch condition

# Load SMO extension
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;
# Servers to check
#$sqlservers = #("$svr", "$svr\$inst");
$sqlservers = Get-Content 'servers.txt'
foreach ($server in $sqlservers) {
$srv = New-Object "Microsoft.SqlServer.Management.Smo.Server" $server;
# Get mirrored databases
$databases = $srv.Databases | Where-Object {$_.IsMirroringEnabled -eq $true};
#Write-Host $databases;
Write-Host "==================================";
# $test= $databases | Select-Object -Property Name, MirroringStatus | Format-Table -AutoSize;
$databases | Select-Object -Property MirroringStatus | Format-Table -AutoSize;
foreach ($status in $databases) {
switch ($databases.MirroringPartnerInstance) {
1 { $status. + "Disconnected" }
2 { $status. + "Suspended" }
2 { $status. + "Synchronizing" }
3 { $status. + "Not Synchronized" }
}
}
I want code like this.
Below is my code if you do this way you will not get issue
function mirroring (
[string] $svr,
[string]$inst,
[string] $datastore
)
{
Set-StrictMode -Version 2
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo")
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended")
$Conn = new-object Microsoft.SqlServer.Management.Common.ServerConnection
$SqlConnection = "Server=$svr\$inst;Database=master;Integrated Security=True;"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = " SELECT db_name(sd.[database_id])AS [Database Name]
,sd.mirroring_state AS [Mirror State]
,sd.mirroring_state_desc AS [Mirror State]
,sd.mirroring_partner_name AS [Partner Name]
,sd.mirroring_role_desc AS [Mirror Role]
,sd.mirroring_safety_level_desc AS [Safety Level]
,sd.mirroring_witness_name AS [Witness]
,sd.mirroring_connection_timeout AS [Timeout(sec)]
FROM sys.database_mirroring AS sd
WHERE mirroring_guid IS NOT null
ORDER BY [Database Name];"
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$DataSet.Tables[0]
$datastore = $DataSet.Tables[0].Rows[0][2]
write-host $datastore
if( $datastore -eq "Disconnected")
{
# test
DisconnectedREMEDIATION
}
elseif($datastore -eq "SYNCHRONIZED")
{
SYNCHRONIZEDREMEDIATION
}
}

Array removed after function?

I am making a script that goes into all servers we're hosting and gets all members of a specific group and the domain name, and then exports it to a file. I'm saving the users and the domain names into two arrays AA (user array) and DA (domain array) AA stands for användararray, and "användare" is users in swedish so it makes sense to me.
I noticed that the export step didn't work, no users or domain names were exported, so I tried to print them in the function. But it doesn't print anything, so I tried to print it in a different location (didn't work). After some experimenting I came to the conlusion that the only place the arrays actually contains any information is inside the foreach loop where I save the users that I find??!
Here is the code
unction GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
#$DA + ";" + $Anvandare.Name
$DA + ";" + $AA
}
}
$output
}
}
$DA
$AA
}
function Export {
Write-Host("C")
$filsökväg = "C:\Users\322sien\Desktop\Coolkids.csv"
$ColForetag = "Företag"
$ColAnvandare = "Användare"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $DomainArray.Length; $p++) {
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
$DA + $delimiter + $AA | Out-File $filsökväg -Append
}
}
ReadInfo
GetData
Export
Can anyone help me with this? I've sat down with this all day and i cant find a solution.
Your variables $DA and $AA are bound to GetData function, so they live only there. You could make them available inside your script by changing it's scope.
Change this:
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
To this:
$script:AA = #($Anvandare.Name)
$script:DA = gc env:UserDomain
So they will now be available for other functions inside the script.
Also I found the ways to improve your script, hope you can see the logic:
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
[array]$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
$array = #()
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$object = New-Object PSObject
$object | Add-Member -MemberType NoteProperty -Name AA -Value #($Anvandare.Name)
$object | Add-Member -MemberType NoteProperty -Name DA -Value (gc env:UserDomain)
$object | Add-Member -MemberType NoteProperty -Name Something -Value $DA + ";" + $AA
$array += $object
}
Write-Output $array
}
Write-Output $output
}
}
}
Your function will now output some data.

Building a wrapper to perform SQL queries from Powershell

I'm trying to build an all-purpose wrapper to easily query a SQL Server database from within Powershell and work with the results:
$result = SQL("SELECT * CustomerID FROM [dbo].[TblCustomers]")
$result.Tables[0] | Foreach {
Write-Host $_.CustomerID
}
Wrapper
## Wrapper for SQL Select statements
#
Function SQL {
param(
[string]$query
)
$Server = "SQLEXPRESS"
$DBase = "DataStore"
$User = "DataUser"
$Pass = "DataPass"
$conn = New-Object System.Data.SqlClient.SqlConnection("Server=$Server;Database=$DBase;User=$User;Password=$Pass;Connect Timeout=15")
try {
$conn.Open()
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
$conn.Close()
}
catch {
$ex = $_.Exception
Write-Error "$ex.Message"
continue
}
return $dataset
}
This only works for SELECT statements however:
If I perform and UPDATE or INSERT, I would like to return the # of rows affected, or an error if applicable
Any other query (ALTER, DROP, DELETE) should be ignored, error message returned
What is the best way to:
Identify the sort of query command (SELECT, DROP)?
Handle all requests in one function? (A switch statement or redirect to specific function?)
Any help is greatly appreciated.
P.S. As I would be the only one using the function in backend scripts, I am not too worried about SQL injection.
[edit]
I now have:
Function Query {
param (
[string]$query
[string]$server
[string]$dbase
[string]$user
[string]$pass
)
if ($user) {
$connstr = "Server={0};Database={1};User ID={2};Password={3};Trusted_Connection=False;Connect Timeout=15" -f $server, $dbase, $user, $pass
}
else {
$connstr = "Server={0};Database={1};Integrated Security=True;Connect Timeout=15" -f $server, $dbase
}
$conn.ConnectionString = $connstr
switch ($query.Split()[0]) {
"SELECT" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
return $dataset
}
"UPDATE" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
return $cmd.ExecuteNonQuery()
}
"INSERT" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
return $cmd.ExecuteNonQuery()
}
}
}
Here is a sample you can build from that uses parameter sets to identify query types. The executenonquery method returns the number of rows affected by the query. I use a validatescript attribute to prevent any query containing the words drop, delete, or alter. You can add others as needed. I don't have a SQL box handy to test with but this should work. You could also adjust this to use parameters for the server, db, user, pass, instead of hardcoding them to make it reusable.
function Invoke-SQLQuery
{
[CmdletBinding(DefaultParameterSetName='SELECT',
SupportsShouldProcess=$true,
ConfirmImpact='Medium')]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName='SELECT')]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName='UPDATE')]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName='INSERT')]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript({$_ -notmatch "ALTER|DROP|DELETE" })]
$Query,
# Param2 help description
[Parameter(ParameterSetName='SELECT')]
[switch]
$Select,
# Param3 help description
[Parameter(ParameterSetName='UPDATE')]
[switch]
$Update,
[Parameter(ParameterSetName='INSERT')]
[switch]
$Insert
)
Begin
{
$Server = "SQLEXPRESS"
$DBase = "DataStore"
$User = "DataUser"
$Pass = "DataPass"
$conn = New-Object System.Data.SqlClient.SqlConnection("Server=$Server;Database=$DBase;User=$User;Password=$Pass;Connect Timeout=15")
}
Process
{
if ($pscmdlet.ShouldProcess("$Server", "Execute Query"))
{
try
{
$conn.Open()
switch($pscmdlet.ParameterSetName){
"SELECT" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
return $dataset
}
"UPDATE" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
return $cmd.ExecuteNonQuery()
}
"INSERT" {
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
return $cmd.ExecuteNonQuery()
}
}
}
catch [System.Data.SqlClient.SqlException]
{
#Implement Error Handling
$ex = $_.Exception
Write-Error "$ex.Message"
continue
}
finally
{
$conn.Close()
$conn.Dispose()
}
}
}
}

Import multisheet excel into sql server and export back to a multisheet excel

I have a multi sheet excel workbook with an unknown number of columns in each sheet. I am looping through each sheet and importing the data into a table in sql server. I then am running a query against that table to pull in a few more fields. I then want the result of that query to be exported into a multi sheet excel workbook. I am struggling with how to export this into a multi sheet workbook. In the code below I have it exporting to a csv, but I'm not sure that is the best way to do it. My plan was to then loop through the csvs to create the xlsx, but I could see that causing problems unless I separate everything into their own directories as this will run many times.
Param(
[String]$excelPath,
[String]$serverName,
[String]$databaseName,
[String]$tableName,
[String]$csvPath
)
$ErrorActionPreference = 'Stop'
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO.SqlDataType') | Out-Null
Trap {
$err = $_.Exception
while ( $err.InnerException )
{
$err = $err.InnerException
Write-Output $err.Message
};
exit 1
}
if (test-path $excelTMGPath ) { rm $excelTMGPath } #delete the file if it already exists
$excel = New-Object -ComObject excel.application
$excel.visible = $False
$excel.displayalerts=$False
$workbook = $excel.Workbooks.Open($ExcelPath)
foreach ($ws in $workbook.Worksheets)
{
$workSheet = $ws.Name
Write-Output "Working on worksheet $workSheet"
$query = "select * from [$workSheet`$]";
$connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$excelPath`";Extended Properties=`"Excel 12.0 Xml;HDR=YES;IMEX=1`";"
# Instantiate some objects which will be needed
$serverSMO = New-Object Microsoft.SqlServer.Management.Smo.Server($serverName)
$db = $serverSMO.Databases[$databaseName];
$newTable = New-Object Microsoft.SqlServer.Management.Smo.Table ;
$newTable.Parent = $db;
$newTable.Name = $tableName ;
$conn = New-Object System.Data.OleDb.OleDbConnection($connectionString)
$conn.open()
$cmd = New-Object System.Data.OleDb.OleDbCommand($query,$conn)
$dataAdapter = New-Object System.Data.OleDb.OleDbDataAdapter($cmd)
$dataTable = New-Object System.Data.DataTable
$dataAdapter.fill($dataTable)
$conn.close()
# Drop the table if it exists
if($db.Tables.Contains($tableName).Equals($true))
{
($db.Tables[$tableName]).Drop()
}
# Iterate the columns in the DataTable object and add dynamically named columns to the SqlServer Table object.
foreach($col in $dataTable.Columns)
{
$sqlDataType = [Microsoft.SqlServer.Management.Smo.SqlDataType]::Varchar
$dataType = New-Object Microsoft.SqlServer.Management.Smo.DataType($sqlDataType);
$dataType.MaximumLength = 1000;
$newColumn = New-Object Microsoft.SqlServer.Management.Smo.Column($newTable,$col.ColumnName,$dataType);
$newColumn.DataType = $dataType;
$newTable.Columns.Add($newColumn);
}
$newTable.Create();
#bcp data into new table
$connectionString = "Data Source=$serverName;Integrated Security=true;Initial Catalog=$databaseName;"
$bc = New-Object ("Data.SqlClient.SqlBulkCopy") $connectionString
$bc.DestinationTableName = "$tableName"
$bc.WriteToServer($dataTable)
#Make sure column 3 is named MasterAccountKey for joining purposes
$sqlColumnRename =
#"
USE $databaseName
declare #MasterAccountKey varchar(255), #cmd varchar(500)
set #MasterAccountKey = (select COLUMN_NAME from INFORMATION_SCHEMA.columns
where table_name = 'zzzExcelSheet'
and ordinal_position = 3);
set #cmd = ('sp_RENAME ''zzzExcelSheet.' + #MasterAccountKey + ''', ''MasterAccountKey'', ''COLUMN''')
exec (#cmd)
"#
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Data Source=$serverName;Initial Catalog=$databaseName;Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $sqlColumnRename
$SqlCmd.Connection = $SqlConnection
$SqlConnection.Open()
$sqlCmd.ExecuteNonQuery()
$SqlConnection.Close()
# Connect to SQL and query data, extract data to SQL Adapter
$SqlQuery = #"
select t.*,
b.Social_Security_Number as SSN,
b.PRIMARY_NAME,
b.ADDR_LINE_1,
b.ADDR_LINE_2,
b.CITY,
b.STATE,
b.ZIP_CODE,
from
other tables b
"#
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Data Source=$serverName;Initial Catalog=$databaseName;Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
Try{
$SqlAdapter.SelectCommand = $SqlCmd
}
Catch
{
exit 1
}
$DataSet = New-Object System.Data.DataSet
$nRecs = $SqlAdapter.Fill($DataSet)
$nRecs | Out-Null
#Populate Hash Table
$objTable = $DataSet.Tables[0]
#Export Hash Table to CSV File
$objTable | Export-CSV $objTable | Export-CSV $csvPath -noType
if (test-path $csvPath ) { rm $csvPath }
}
$ws = $null
$workSheet = $null
$workbook.Close()
$workbook = $null
$excel.quit()
while ([System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($excel)) {}
$excel = $null

Resources