Good morning everyone. I am currently in school, working on a PowerShell project, part 2. I was able to get the script to work but now I'm being asked to do this, Check for the existence of a database named ClientDB. Output a message to the console that indicates if the database exists or if it does not. If it already exists, delete it and output a message to the console that it was deleted.Create a new database named “ClientDB” on the Microsoft SQL server instance. Output a message to the console that the database was created. Then go through the rest in creating the table and importing the csv as I've done that much. My problem is how to use the invoke-sqlcmd in an if/else, else/if statement. Thank you so much.
Try {
$DBname = "ClientDB"
$SqlServer = ".\SQLEXPRESS"
if([bool] (Get-SQLDatabase -Filter * | ? {$SqlServer -eq $DBname} ))
{ Write-Host 'ClientDB exists' }
else { Write-Host 'ClientDB does not exists' }
$CreateTableQuery = #"
use $DBname
CREATE TABLE Client_A_Contacts
(
first_name varchar(20),
last_name varchar(20),
city varchar(20),
county varchar(20),
zip varchar(20),
officePhone varchar(20),
mobilePhone varchar(20)
)
"#
Import-Module sqlps -DisableNameChecking
$ServerObject = New-Object Microsoft.SqlServer.Management.Smo.Server($SqlServer)
$DBobject = New-Object Microsoft.SqlServer.Management.Smo.Database($ServerObject, $DBname)
$DBobject.Create()
Write-Host $DBobject "create success # " $DBobject.CreateDate
Invoke-sqlcmd -ServerInstance $SqlServer -Database $DBname -Query $CreateTableQuery
Pop-Location
Import-Csv "NewClientData.csv" | ForEach-Object {Invoke-sqlcmd -ServerInstance $SqlServer -
Database $DBname -Query "insert into dbo.Client_A_Contacts values
('$($_.first_name)','$($_.last_name)','$($_.city)','$($_.county)',
'$($_.zip)','$($_.officePhone)','$($ _.mobilePhone)')"}
}
catch [System.OutOfMemoryException] {
Write-Host "Caught: System.OutOfMemoryException"
}
Invoke-Sqlcmd -Database ClientDB –ServerInstance .\SQLEXPRESS -Query ‘SELECT * FROM dbo.Client_A_Contacts’ > .\SqlResults.txt
Related
I am trying to run a PowerShell script where I query a list of servers and run a custom Get-TimeZone function and then send an email to a list of recipients with the data in a table format.
I was able to get the script to work correctly if I write the server list to a text file and then run the function against the list from the text file, but I am wanting to skip the step of writing to a text file and run the function directly against the returned list of servers from the query.
##This works via text file
$SQLInstance = "SQLInstance"
$Database = "DatabaseName"
$ServerQuery = "SELECT em.ServerName FROM dbo.EnvironmentMap AS em ORDER BY em.ServerName;"
$serverFile = "D:\Powershell\Misc\TimeZoneServers.txt"
If ( Test-Path $serverFile ) { Remove-Item $serverFile }
$servers = Invoke-DbaQuery -SqlInstance $SQLInstance -Database $Database -Query $ServerQuery
foreach ($server in $servers.ServerName)
{
$server | Out-File -FilePath $serverFile -Append
}
$serverNames = Get-Content $serverFile
$TimeZoneInfo = Get-TimeZone -ComputerName $serverNames | Select ComputerName, TimeZone | Sort-Object ComputerName, TimeZone | ConvertTo-Html | Out-String
$messageParameters = #{
Subject = "SQL Server Time Zone Information"
Body = $TimeZoneInfo
From = "noreply#companyX.com"
To = "email1#companyX.com"
SmtpServer = "smtp.companyX.com"
}
Send-MailMessage #messageParameters -BodyAsHtml
In my attempt to remove the text file creation, the script does run but it sends an email with a single value for each of the servers that is returned.
##Sends multiple emails
$SQLInstance = "SQLInstance"
$DatabaseName = "DatabaseName"
$TimeZoneQuery = "SELECT em.ServerName FROM dbo.EnvironmentMap AS em ORDER BY em.ServerName;"
$serverList = Invoke-DbaQuery -SqlInstance $SQLInstance -Database $DatabaseName -Query $TimeZoneQuery
foreach ( $server in $serverList )
{
$TimeZoneInfo = Get-TimeZone -ComputerName $server.ServerName | Select ComputerName, TimeZone | Sort-Object ComputerName, TimeZone | ConvertTo-Html | Out-String
$messageParameters = #{
Subject = "SQL Server Time Zone Information"
Body = $TimeZoneInfo
From = "noreply#companyX.com"
To = "email1#companyX.com"
SmtpServer = "smtp.companyX.com"
}
Send-MailMessage #messageParameters -BodyAsHtml
}
I understand why the code now sends multiple emails - but I cannot figure out where or if I need a foreach loop to have the function run for each of the servers that are returned in the query.
Any advice on how I can have one email sent that would contain all of the servers returned from the query?
Thanks,
I am trying to a single value from a SQL query to a PowerShell variable. I know the connection and query are running successfully because I am getting the output I expected when using Invoke-DbaQuery or Invoke-Sqlcmd. But when I try to assign the query results to a variable, all I see is System.Data.DataRow
Any suggestions on how to get this query's results assigned to a variable?
$SQLInstance = "SQLInstance"
$Database = "DatabaseName"
$Query = "SELECT TOP (1) Password FROM dbo.Connections WHERE VendorName = 'YYY' ORDER BY VendorName;"
#Invoke-DbaQuery -SqlInstance $SQLInstance -Database $Database -Query $Query
#$password = (Invoke-DbaQuery -SqlInstance $SQLInstance -Database $Database -Query $Query)
$password = Invoke-Sqlcmd -ServerInstance $SQLInstance -Database $Database -Query $Query
Write-Host $password
You've seen that $password is a [DataRow], and Write-Host can only use [string]. Try one of these two:
Output the result by itself (no write-host). Powershell will format all of the fields and rows for you:
$password
# outputs:
password
--------
hunter2
Output just the string from your query result instead of the table format above:
$password.Password
# Outputs:
hunter2
DataRow objects are made to contain multiple rows and multiple fields, so it can get confusing when you're expecting just a single value as a result.
I have this PowerShell script:
$QUERY = "SELECT name FROM sys.databases";
$Databases = invoke-sqlcmd -serverinstance "SQLInstanceName" -database "master" -Query $QUERY
foreach ($dbname in $Databases)
{
$dbname
}
Let's say I want the recovery model for the databases as below, how do I get them into PowerShell variables?
$QUERY = "SELECT name, recovery_model_desc FROM sys.databases";
May be you are looking for
$QUERY = "SELECT * from sys.databases";
$Databases = invoke-sqlcmd -serverinstance "SQLInstanceName" -database "master" -Query $QUERY
# This is just to print the individual value of column (name and recovery_model)
$Databases | ForEach {
Write-Host '----------------------------------------'
Write-Host 'Database :' $_.name
Write-Host 'Recovery model :' $_.recovery_model
Write-Host '----------------------------------------'
Write-Host
}
# This will give you name and recovery_model of database
$Databases | Select name, recovery_model
I am running a powershell script for 'dropping few databases' that are not in sync in my secondary server. (please refer the code snippet below)
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)] $DatabaseName
)
$serverInstanceP = "SQLServerTest"
Write-Host "Primary Server Instance is:"$serverInstanceP
#Validating whether the database name is empty string or `NULL` value
if (![string]::IsNullOrEmpty($DatabaseName)) {
#Validating the presence of any special charater mentioned in the database name except "_" (uderscore)
if ($DatabaseName -notmatch "[^a-zA-Z0-9_]") {
Write-Host "A valid database has been provided, which is not null, nor an empty string neither contain any special character"
}
else {
Write-Warning "Database name should not contain any special character"
}
}
else {
Write-Warning "Database name cannot be NULL"
}
Write-Host $DatabaseName "is the current database"
$syncQuery = "
:Connect SQLServerTest
USE [master]
GO
DROP DATABASE [$($DatabaseName)];
GO "
Invoke-Sqlcmd -ServerInstance $serverInstanceP -Database master -Query $syncQuery -QueryTimeout 1200
While running the above script it is giving me an error 'ParserError: (:) [Invoke-Sqlcmd], ParserException'.
Especially ':Connect SQLServerTest'
Do I need to send my query through #' '#
But when I run the same query on the database server through SQLCMD, it is working fine.
Unfortunately, the command ":CONNECT" is not supported by "invoke-sqlcmd".
https://learn.microsoft.com/en-us/powershell/module/sqlserver/invoke-sqlcmd?view=sqlserver-ps
The connection is done with the parameters "-ServerInstance $serverInstanceP -Database master".
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 {