AzureSQL Multiple Invoke-SQLCmd Loop and Connection Errors - powershell

I am attempting to loop through an invoke-sqlcmd for multiple AzureSQL databases via Azure Automation. The first item in the loop executes, but the all the rest fail with a:
Invoke-Sqlcmd : A network-related or instance-specific error occurred
while establishing a connection to SQL Server. The server was not
found or was not accessible. Verify that the instance name is correct
and that SQL Server is configured to allow remote connections.
(provider: Named Pipes Provider, error: 40 - Could not open a
connection to SQL Server)
I am guessing that I need to close the connection from the first invoke-sqlcmd before executing the next, but have not found a direct method to accomplish that with invoke-sqlcmd. Here is my loop:
param(
# Parameters to Pass to PowerShell Scripts
[parameter(Mandatory=$true)][String] $azureSQLServerName = "myazuresql",
[parameter(Mandatory=$true)][String] $azureSQLCred = "myazureautosqlcred"
)
# DB Name Array
$dbnamearray = #("database1","database2","database3")
$dbnamearray
# Datatable Name
$tabName = "RunbookTable"
#Create Table object
$table = New-Object system.Data.DataTable "$tabName"
#Define Columns
$col1 = New-Object system.Data.DataColumn dbname,([string])
#Add the Columns
$table.columns.add($col1)
# Add Row and Values for dname Column
ForEach ($db in $dbnamearray)
{
$row = $table.NewRow()
$row.dbname = $db
#Add the row to the table
$table.Rows.Add($row)
}
#Display the table
$table | format-table -AutoSize
# Loop through the datatable using the values per column
$table | ForEach-Object {
# Set loop variables as these are easier to pass then $_.
$azureSQLDatabaseName = $_.dbname
# Execute SQL Query Against Azure SQL
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
$Cred = Get-AutomationPSCredential -Name $azureSQLCred
$SQLOutput = $(Invoke-Sqlcmd -ServerInstance $azureSQLServerName -Username $Cred.UserName -Password $Cred.GetNetworkCredential().Password -Database $azureSQLDatabaseName -Query "SELECT * FROM INFORMATION_SCHEMA.TABLES " -QueryTimeout 65535 -ConnectionTimeout 60 -Verbose) 4>&1
Write-Output $SQLOutput
}

You can try making each connection as a powershell job. This solved a very similar issue I had some time ago. Send-MailMessage closes every 2nd connection when using attachments If you want to read an explanation. Basically, if you're unable to use a .Close() method, you can force connections to close by terminating the entire session for each run. In an ideal world the cmdlet would handle all this for you, but not everything was created perfectly.
# Loop through the datatable using the values per column
$table | ForEach-Object {
# Set loop variables as these are easier to pass then $_.
$azureSQLDatabaseName = $_.dbname
# Execute SQL Query Against Azure SQL
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
$Cred = Get-AutomationPSCredential -Name $azureSQLCred
# Pass in the needed parameters via -ArgumentList and start the job.
Start-Job -ScriptBlock { Write-Output $(Invoke-Sqlcmd -ServerInstance $args[0] -Username $args[1].UserName -Password $args[1].GetNetworkCredential().Password -Database $args[0] -Query "SELECT * FROM INFORMATION_SCHEMA.TABLES " -QueryTimeout 65535 -ConnectionTimeout 60 -Verbose) 4>&1 } -ArgumentList $azureSQLServerName, $Cred | Wait-Job | Receive-Job
}
This is untested since I do not have a server to connect to, but perhaps with a bit of work you can make something out of it.

I faced the same issue previously while doing something with the database of azure sql. You can try this
1. Create Automation Account
New-AzureRmAutomationAccount -ResourceGroupName $resourceGroupName -Name $automationAccountName -Location $location
2. Set the Automation account to work with
Set-AzureRmAutomationAccount -Name $automationAccountName -ResourceGroupName $resourceGroupName
3. Create / Import a Runbook
Here we already have a runbook ready so we import it. Here's the runbook code
workflow runbookNameValue
{
inlinescript
{
$MasterDatabaseConnection = New-Object System.Data.SqlClient.SqlConnection
$MasterDatabaseConnection.ConnectionString = "ConnectionStringValue"
# Open connection to Master DB
$MasterDatabaseConnection.Open()
# Create command
$MasterDatabaseCommand = New-Object System.Data.SqlClient.SqlCommand
$MasterDatabaseCommand.Connection = $MasterDatabaseConnection
$MasterDatabaseCommand.CommandText = "Exec stored procedure"
# Execute the query
$MasterDatabaseCommand.ExecuteNonQuery()
# Close connection to Master DB
$MasterDatabaseConnection.Close()
}
}
4. Importing
Import-AzureRMAutomationRunbook -Name $runBookName -Path $scriptPath -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Type PowerShell
I hope this helps. Instead of using Invoke-Sqlcmd use the $MasterDatabaseCommand.ExecuteNonQuery() like i've provided in the runbook. It will work

It seems that you append .database.windows.net to the server name inside the loop. I guess that's why it works for the first iteration only.
Just move this line:
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
before this line:
$table | ForEach-Object {

Related

Create printers from an imported csv file to multiple print servers

I have been working with this script and have successfully grabbed info from a .csv file and added it to one print server.
Right now I have the print server hard coded in the script and it allows me to add multiple print servers into the script, but I would like to add the print servers to a column in my .csv file and read from there to eliminate the static servers in the code. Here is what I have:
The second part I am struggling with is publishing and not publishing printers ( listing in AD or not ) I was thinking of adding another column called published. Then creating an if/then to publish or not publish**
foreach ($server in #("printserver1")) {
foreach ($printer in #(Import-Csv C:\PrinterList.csv)) {
Add-PrinterPort -ComputerName $server -Name $printer.IPAddress -PrinterHostAddress $printer.IPAddress
Add-Printer -ComputerName $server -Name $printer.Printername -DriverName $printer.Driver -PortName $printer.IPAddress -Comment $printer.Comment -Location $printer.Location -Shared -ShareName $printer.Printername -Published
}
}
If PrinterList.csv contains a column called Publish with False or True as possible values, you can do the following:
foreach ($printer in (Import-Csv C:\PrinterList.csv)) {
$Params = #{ ComputerName = $server
Name = $printer.Printername
DriverName = $printer.Driver
PortName = $printer.IPAddress
Comment = $printer.Comment
Location = $printer.Location
ShareName = $printer.Printername
}
Add-Printer #Params -Shared -Published:([bool]::Parse($printer.Publish))
}
Since Publish is a [switch] parameter, you can use the syntax -Publish:$true or -Publish:$false. The Parse() method parses a string value into a boolean value.
$Params Splatting is not necessary here. It just provides a bit more readability.
Alternatively, [System.Convert]::ToBoolean($printer.Publish) has the same result in the proposed scenario but does offer more flexibility as [System.Convert]::ToBoolean(0) returns False and [System.Convert]::ToBoolean(1) returns True.

Code not being seen if I include it from a file

I'm writing some Powershell scripts to manage a SQL Server. When I create my SMO object, I do a test to see if I can list my databases. If so, then I assume I'm connected and call a function (via an included file) that has an Invoke-Sqlcmd that causes my database to disconnect or something.
However, if I run the invoke command directly in the script, it works fine.
Looking at the provided code, my output is as follows:
I'm connected
$SqlServer is not contactable
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
$ErrorActionPreference = "Stop"
Import-Module 'sqlps' -DisableNameChecking #load all of SMO
$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -
Parent
try {
("$ScriptDirectory\_HRbackup.ps1")
("$ScriptDirectory\_HRprinter.ps1")
("$ScriptDirectory\_HRbody.ps1")
} catch {
Write-Host "Error while loading supporting PowerShell Scripts"
}
$ServerList = $env:COMPUTERNAME
$SqlServer = New-Object ('Microsoft.SqlServer.Management.Smo.Server')
$ServerList # add server list
try {
$databases = $SqlServer.Databases
if ($databases.Count -gt 0) {
Write-Host "I am connected"
Backup-Html
} else {
$login = Get-Credential -Message 'Please prov#ide your SQL
Credentials'
$SqlServer.ConnectionContext.LoginSecure = $false
$SqlServer.ConnectionContext.Login = $login.UserName
$SqlServer.ConnectionContext.set_SecurePassword($login.Password)
$SqlServer.ConnectionContext.connect()
}
} catch {
$WarningPreference = 'continue'
Write-Warning "$SqlServer is not contactable"
#$SqlServer.ConnectionContext.Disconnect()
} finally {
$SqlServer.ConnectionContext.Disconnect()
}
Here is the content of Backup-Html:
$Query = "select top 5 a.server_name, a.database_name,
backup_finish_date, a.backup_size,
CASE a.[type] -- Let's decode the three main types of
backup here
WHEN 'D' THEN 'Full'
WHEN 'I' THEN 'Differential'
WHEN 'L' THEN 'Transaction Log'
ELSE a.[type]
END as BackupType
-- Build a path to the backup
,'\\' +
-- lets extract the server name out of the recorded
server and instance name
CASE
WHEN patindex('%\%',a.server_name) = 0 THEN
a.server_name
ELSE
substring(a.server_name,1,patindex('%\%',a.server_name)-1)
END
-- then get the drive and path and file information
+ '\' + replace(b.physical_device_name,':','$') AS
'\\Server\Drive\backup_path\backup_file'
from msdb.dbo.backupset a join msdb.dbo.backupmediafamily
b
on a.media_set_id = b.media_set_id
where a.database_name Like 'Easy%'
order by a.backup_finish_date desc"
#Use SQLCmd to execute the query on the server
Invoke-Sqlcmd -ServerInstance $SQLServer -Query $Query

Powershell remote exchange session missing types

I'd like to collect and process some data from Exchange on remote server using powershell script. Script will be executed on host A, it will create session to host B, inside this session it will create another session - to exchange endpoint.
I'm doing it this way for security reasons, Exchange session needs to be created locally. The issue is that my first session doesn't have exchange data types, type of my $data object
is PSObject, but it should be MailboxDatabase. Is there a way to somehow import data types needed?
$sessWsman = GetOrCreateSession $ip $login $uri $password $configurationName $retryConnectUsingFdqn $uriTemplate $macroMissingWindowsCredentials $macroUnableConnect $macroSessionsDepleted $macroSessionClosed $null
Invoke-Command -Session $sessWsman -Args #("ENG-AUS-SAM-11") -ScriptBlock {
$sessExchange = GetOrCreateSession $ip $login $uri $password $configurationName $retryConnectUsingFdqn $uriTemplate $macroMissingWindowsCredentials $macroUnableConnect $macroSessionsDepleted $macroSessionClosed $sessWsman
if ( -Not ([bool](Get-Command -Name 'Get-MailboxDatabase' -ErrorAction SilentlyContinue)))
{
Import-PSSession $sessExchange -FormatTypeName * -CommandName Get-MailboxDatabase | Out-null
}
$data = Get-MailboxDatabase -Server $HostName -Status
$data.GetType()
}
I tried adding this Add-PSSnapin Microsoft.Exchange* after importing Get-MailboxDatabase, but it didn't helped.

How do I reliably get variable from child

I am building a sql Job Generator in Powershell. My PS skills aren't the greatest,
I want to get a value created in function CreateSqlTask. The variable I want is $job. I get an array of objects back from this function.
#My call
$returnParams = CreateSqlTask ( LIST OF PARAMS)
$returnParams[0] is the value of the jobSchedule Creation $returnParams[1] is the variable I want, this is the value of $job .
as a programmer I do not believe it is relable to just assume $returnParams[1] is always the variable I need. What is the proper way to handle this case?
#Here is the function implementation:
function CreateSqlTask
{
Param ( LIST OF PARAMS )
#Make all errors terminating
$ErrorActionPreference = "Stop"
#Create the SQL Job
$job = CreateSqlJob -serverInstance $serverInstance -jobName $jobName -jobDesc $jobDesc -jobCategory $jobCategory -jobAlertOperator $jobAlertOperator -jobEmailLevel $jobEmailLevel
#Create the SQL Job Step
$jobStep = CreateSqlJobStep $job $stepName $stepCmd
#Alter the Job to tell it what step should execute first
$job.StartStepID = $jobStep.ID
$job.Alter()
#Create the SQL Job Schedule
CreateSqlJobSchedule $job `
$schedName `
$schedFreqType `
$schedFreqRecurFactor `
$schedFreqInterval `
$schedFreqSubDayType `
$schedFreqSubDayInterval `
$startingSchedHour `
$startingSchedMinute `
$endingSchedHour `
$endingSchedMinute
return $job
}
A function should return only one type of object. If you don't need the job creation return, you can send that output to $null or assign it to a variable within the function. If you do need that along with the other information I'd create a custom object or hash table that includes that information along with the job information and return that.
As you have pointed out, both CreateSqlJobSchedule and return $job are returning values. If you need both of these then I suggest you assign these to a new object:
$jobDetails = #{
CreateSqlJob = $CreateSqlJob
CreateSqlJobSchedule = $CreateSqlJobSchedule
}
return $jobDetails
The above assumes you have assigned the two calls to two variables, you can then refer to them by name:
$createSqlTaskResults = CreateSqlTask
$createSqlTaskResults.CreateSqlJob
$createSqlTaskResults.CreateSqlJobSchedule
Here's how it would look in your example:
#Here is the function implementation:
function CreateSqlTask
{
Param ( LIST OF PARAMS )
#Make all errors terminating
$ErrorActionPreference = "Stop"
#Create the SQL Job
$CreateSqlJob = CreateSqlJob -serverInstance $serverInstance -jobName $jobName -jobDesc $jobDesc -jobCategory $jobCategory -jobAlertOperator $jobAlertOperator -jobEmailLevel $jobEmailLevel
#Create the SQL Job Step
$jobStep = CreateSqlJobStep $job $stepName $stepCmd
#Alter the Job to tell it what step should execute first
$job.StartStepID = $jobStep.ID
$job.Alter()
#Create the SQL Job Schedule
$CreateSqlJobSchedule = CreateSqlJobSchedule $job `
$schedName `
$schedFreqType `
$schedFreqRecurFactor `
$schedFreqInterval `
$schedFreqSubDayType `
$schedFreqSubDayInterval `
$startingSchedHour `
$startingSchedMinute `
$endingSchedHour `
$endingSchedMinute
$jobDetails = #{
CreateSqlJob = $CreateSqlJob
CreateSqlJobSchedule = $CreateSqlJobSchedule
}
return $jobDetails
}
One option you have is within the CreateSQLJob function to return a custom object that includes the pertinent information you want.
For instance:
$job = New-Object Object
$obj | Add-Member Noteproperty serverInstance -value $serverInstance
$obj | Add-Member Noteproperty jobName -value $jobName
Then you would be able to call into those properties:
$obj.jobName
The above is a contrived example.

How do you run a SQL Server query from PowerShell?

Is there a way to execute an arbitrary query on a SQL Server using Powershell on my local machine?
For others who need to do this with just stock .NET and PowerShell (no additional SQL tools installed) here is the function that I use:
function Invoke-SQL {
param(
[string] $dataSource = ".\SQLEXPRESS",
[string] $database = "MasterData",
[string] $sqlCommand = $(throw "Please specify a query.")
)
$connectionString = "Data Source=$dataSource; " +
"Integrated Security=SSPI; " +
"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()
$dataSet.Tables
}
I have been using this so long I don't know who wrote which parts. This was distilled from others' examples, but simplified to be clear and just what is needed without extra dependencies or features.
I use and share this often enough that I have turned this into a script module on GitHub so that you can now go to your modules directory and execute git clone https://github.com/ChrisMagnuson/InvokeSQL and from that point forward invoke-sql will automatically be loaded when you go to use it (assuming your using PowerShell v3 or later).
You can use the Invoke-Sqlcmd cmdlet
Invoke-Sqlcmd -Query "SELECT GETDATE() AS TimeOfQuery;" -ServerInstance "MyComputer\MyInstance"
http://technet.microsoft.com/en-us/library/cc281720.aspx
This function will return the results of a query as an array of powershell objects so you can use them in filters and access columns easily:
function sql($sqlText, $database = "master", $server = ".")
{
$connection = new-object System.Data.SqlClient.SQLConnection("Data Source=$server;Integrated Security=SSPI;Initial Catalog=$database");
$cmd = new-object System.Data.SqlClient.SqlCommand($sqlText, $connection);
$connection.Open();
$reader = $cmd.ExecuteReader()
$results = #()
while ($reader.Read())
{
$row = #{}
for ($i = 0; $i -lt $reader.FieldCount; $i++)
{
$row[$reader.GetName($i)] = $reader.GetValue($i)
}
$results += new-object psobject -property $row
}
$connection.Close();
$results
}
Here's an example I found on this blog.
$cn2 = new-object system.data.SqlClient.SQLConnection("Data Source=machine1;Integrated Security=SSPI;Initial Catalog=master");
$cmd = new-object system.data.sqlclient.sqlcommand("dbcc freeproccache", $cn2);
$cn2.Open();
if ($cmd.ExecuteNonQuery() -ne -1)
{
echo "Failed";
}
$cn2.Close();
Presumably you could substitute a different TSQL statement where it says dbcc freeproccache.
If you want to do it on your local machine instead of in the context of SQL server then I would use the following. It is what we use at my company.
$ServerName = "_ServerName_"
$DatabaseName = "_DatabaseName_"
$Query = "SELECT * FROM Table WHERE Column = ''"
#Timeout parameters
$QueryTimeout = 120
$ConnectionTimeout = 30
#Action of connecting to the Database and executing the query and returning results if there were any.
$conn=New-Object System.Data.SqlClient.SQLConnection
$ConnectionString = "Server={0};Database={1};Integrated Security=True;Connect Timeout={2}" -f $ServerName,$DatabaseName,$ConnectionTimeout
$conn.ConnectionString=$ConnectionString
$conn.Open()
$cmd=New-Object system.Data.SqlClient.SqlCommand($Query,$conn)
$cmd.CommandTimeout=$QueryTimeout
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
[void]$da.fill($ds)
$conn.Close()
$ds.Tables
Just fill in the $ServerName, $DatabaseName and the $Query variables and you should be good to go.
I am not sure how we originally found this out, but there is something very similar here.
There isn't a built-in "PowerShell" way of running a SQL query. If you have the SQL Server tools installed, you'll get an Invoke-SqlCmd cmdlet.
Because PowerShell is built on .NET, you can use the ADO.NET API to run your queries.
Invoke-Sqlcmd -Query "sp_who" -ServerInstance . -QueryTimeout 3
To avoid SQL Injection with varchar parameters you could use
function sqlExecuteRead($connectionString, $sqlCommand, $pars) {
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$connection.Open()
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand, $connection)
if ($pars -and $pars.Keys) {
foreach($key in $pars.keys) {
# avoid injection in varchar parameters
$par = $command.Parameters.Add("#$key", [system.data.SqlDbType]::VarChar, 512);
$par.Value = $pars[$key];
}
}
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
$connection.Close()
return $dataset.tables[0].rows
}
$connectionString = "connectionstringHere"
$sql = "select top 10 Message, TimeStamp, Level from dbo.log " +
"where Message = #MSG and Level like #LEVEL"
$pars = #{
MSG = 'this is a test from powershell'
LEVEL = 'aaa%'
};
sqlExecuteRead $connectionString $sql $pars
You can even format string and pass parameters as you want.
case "ADDSQLSERVERUSER":
//0 = coprorateName;
//1 = user password
//2 = servername
command = #"$sqlQuery = Use JazzUWS_'{0}'
Create login UWSUser_'{0}' with password='{1}';
Create user UWSUser_'{0}' for login UWSUser_'{0}';
Grant Execute to UWSUser_'{0}';
Use ReportSvrUWS_'{0}'
Create user UWSUser_'{0}' for login UWSUser_'{0}';
Grant Execute to UWSUser_'{0}';
Invoke-Sqlcmd -Query $sqlQuery -ServerInstance '{2}'";
break;
C# Code for remote execution(you can organize your way)
string script = PowershellDictionary.GetPowershellCommand("ADDSQLSERVERUSER");
script = String.Format(script, this.CorporateName, password, this.SQLServerName)
PowerShellExecution.RunScriptRemote(_credentials.Server, _credentials.Username, _credentials.Password, new List<string> { script });
You could use the best SQL Server module around: DBATOOLS. You would also benefit from running a query to multiple sql instances.
Install-Module dbatools -Scope CurrentUser
$sql = 'SQL1','SQL1\INSTANCE1','SQL2'
$query = "SELECT 'This query would run on all SQL instances'"
Invoke-DbaQuery -SqlInstance $sqlinstances -Query $query -AppendServerInstance