Powershell: Find all SQL Server instances per ser server? - sql-server

Strictly speaking, the question is, I use this solution and it works, but is there a better way?
With the following caveats.
1) I don't want to do a network wide search for SQL instances, I am interrogating known SQL servers, but I want to grab the instance names on each.
2) The code assumes Microsoft will never change the display name for the SQL Server Service.
function getSQLInstance ([string]$SERVER) {
$services = Get-Service -Computer $SERVER
# Filter for SQL services
$services = $services | ? DisplayName -like "SQL Server (*)"
# Remove MSSQL$ qualifier to get instance name
try {
$instances = $services.Name | ForEach-Object {($_).Replace("MSSQL`$","")}
}catch{
# Error if none found
return -1
}
return $instances
}
getSQLInstance "YOUR_SERVER"

Rather than re-invent the wheel, take a look at how SQL Power Doc discovers instances on a server. Which, from looking at NetworkScan.psm1, appears to be very similar to your approach:
$ManagedComputer.Services | ForEach-Object {
if (($_.Name).IndexOf('$') -gt 0) {
$InstanceName = ($_.Name).Substring(($_.Name).IndexOf('$') + 1)
$IsNamedInstance = $true
$ManagedComputerServerInstanceName = $InstanceName
} else {
$InstanceName = $null
$IsNamedInstance = $false
$ManagedComputerServerInstanceName = $_.Name
}
Or, just use SQL Power Doc and point it at specific server names to collect this and more data about the instances.

Related

Loop array and store all results

Admittedly I am not a strong developer but I have done some research and I want to get a solid answer to my problem. I see Multi-dimensional Arrays but I am not sure if this is the right answer
I have a three part issue. Database Server, Database, and Units
There are n Units, there are 4 Database Servers, there are n database (1 database per 1 Unit).
So for example:
Unit1 is on Database Server 4 using Database DB_Unit1
Unit2 is on Database Server 4 using Database DB_Unit2
Unit3 is on Database Server 2 using Database Unit3 (Some Databases are not named DB_Unit)
Unit4 is on Database Server 1 using Database XYZ
Unit5 is on Database Server 1 using Database DB_Unit5
I assumed I could use an array to store each string for each Agency but I'm not sure how that works.
So I am trying to write a PowerShell script that uses all of these functions
$units = ("Unit1","Unit2","Unit3","Unit4","Unit5")
FOREACH ($Unit in Units){
Invoke-Sqlcmd -ServerInstance $DatabaseServer -Database $Database -Query "Select * from tbl1"
}
My outcome is that it would Query each Database Server with the assigned database for each Unit.
Any ideas on how this works with an Array or is there a separate way to associate this data?
I think this might be more what you are after. It should run n times, where n is the number of strings in $Units. There should be 5 results added to $SQLResults.
$Units= ("Unit1","Unit2","Unit3","Unit4","Unit5")
$SQLResults = New-Object System.Collections.ArrayList
ForEach ($Unit in $Units){
switch ($Unit) {
"Unit1" { $DatabaseServer = "Database Server 4";$Database = "DB_Unit1" }
"Unit2" { $DatabaseServer = "Database Server 4";$Database = "DB_Unit2" }
"Unit3" { $DatabaseServer = "Database Server 2";$Database = "Unit3" }
"Unit4" { $DatabaseServer = "Database Server 1";$Database = "XYZ" }
"Unit5" { $DatabaseServer = "Database Server 1";$Database = "DB_Unit5" }
}
$UnitResults = Invoke-Sqlcmd -ServerInstance $DatabaseServer -Database $Database -Query "Select * from tbl1"
$UnitResults | Add-Member -MemberType NoteProperty -Name "Unit" -Value $Unit
$SQLResults.Add($UnitResults)
}
You should then be able to get the specific unit results by doing $SQLResults | where {$_.Unit -eq "Unit1"}

Replace backup location in SQL jobs

We are trying to replace a backup location in a SQL Backup Jobs step (running power shell through several servers)
Below is a PS script i would like to use it:
# $Server is a file with SERVERNAME names
$Jobs = Get-SQLAgentJob -ServerInstance
$Servers Foreach ($job in $Jobs.Where{$_.Name -like 'DatabaseBackup' -and $_.isenabled -eq $true}) {
foreach ($Step in $Job.jobsteps.Where{$_.Name -like 'DatabaseBackup'}) {
$Step.Command = $Step.Command.Replace("Directory = N'C:\Backup\oldname1\oldname2\SERVERNAME'", "Directory = N'C:\Backup2\newname1\newname2\SERVERNAME'")
$Step.Alter()
}
}
It seems like this should work. The only potential problems I see are the following:
named SQL instances: The $servers variable will need to have the servername\instancename format if not using the default instance name
Job and step names: If your job names and job step names are not exactly databasebackup, case excluded, then the -like operator combined with the exact string will not find a match. If the names contain the databasebackup string, you will be safer to use -match "databasebackup" or -like with asterisks on both sides of the string.
Otherwise, this code should just work provided there are not network connectivity or permissions issues.

Collecting data from SQL Server using SMO is very slow

I got this Powershell script that gets information about whether SQL Server is installed on a host and gets the SQL Server version and other details.
It takes the list of hosts from a txt file and saves the information to a DataTable.
$data = New-Object ('System.Data.DataTable')
$data.Columns.Add('Host name') | Out-Null
$data.Columns.Add('Ip Address') | Out-Null
$data.Columns.Add('SQL Server Product Name') | Out-Null
$data.Columns.Add('SQL Server Edition') | Out-Null
$data.Columns.Add('SQL Server Version') | Out-Null
$data.Columns.Add('SQL Server Type') | Out-Null
$data.Columns.Add('SQL Server Status') | Out-Null
Get-Content .\servers.txt | ForEach {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
$row = $data.NewRow()
$row['Host name'] = $_
try {
$row['Ip Address'] = [Net.Dns]::GetHostEntry($_).AddressList.IpAddressToString
}
catch [System.Net.Sockets.SocketException] {
$row['Ip Address'] = 'Offline'
}
If ($row['Ip Address'] -eq 'Offline') {
$row['SQL Server Product Name'] = 'N/A'
$row['SQL Server Edition'] = 'N/A'
$row['SQL Server Version'] = 'N/A'
$row['SQL Server Type'] = 'N/A'
$row['SQL Server Status'] = 'N/A'
}
else {
$smo = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $_
$row['SQL Server Product Name'] = $smo.Product + ' ' + $smo.ProductLevel
$row['SQL Server Edition'] = $smo.Edition
$row['SQL Server Version'] = $smo.VersionString
$row['SQL Server Type'] = $smo.ServerType
$row['SQL Server Status'] = $smo.Status
}
$smo.ConnectionContext.Disconnect()
$data.Rows.Add($row)
}
$data | Format-Table -AutoSize
The problem with this script is that it takes a long time to run (more than an hour with a list of 113 servers).
Is there some way to speed up the process?
You could run your script asynchronously using background jobs ( you will have to use 3 cmdlets : start-job, get-job and receive-job).
Quoted from About_remote_jobs
START A REMOTE JOB THAT RETURNS THE RESULTS TO THE LOCAL COMPUTER (ASJOB)
To start a background job on a remote computer that returns the command
results to the local computer, use the AsJob parameter of a cmdlet such
as the Invoke-Command cmdlet.
When you use the AsJob parameter, the job object is actually created on
the local computer even though the job runs on the remote computer. When
the job is completed, the results are returned to the local computer.
You can use the cmdlets that contain the Job noun (the Job cmdlets) to
manage any job created by any cmdlet. Many of the cmdlets that have
AsJob parameters do not use Windows PowerShell remoting, so
you can use them even on computers that are not configured for
remoting and that do not meet the requirements for remoting.

How to scan SQL Server error log (and ignore some errors) with PowerShell?

I need a PowerShell script (2.0 compatible) to scan SQL Server 2008 R2 and later error logs. I need to have a list of phrases to search for, and a list of phrases to exclude.
param ([String]$instanceName=$(throw "Instance name was not supplied"))
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO')|Out-Null;
$sqlServer = new-object ("Microsoft.SqlServer.Management.Smo.Server") $instanceName;
$r = $sqlServer.ReadErrorLog();
$find = "Error:","Failed";
$exclude = "Error: 0x2098";
# need to do something with $r here and involve $find and $exclude
So, for example I want to find all lines in the ERRORLOG that contain Error: and Failed, but exclude the ones in the $exclude array. Any ideas?
You can find a lot of info on this here:
Use PowerShell to Parse SQL Server 2012 Error Logs
But one way to do what you are asking is by filtering the results when setting $r
$r = $sqlServer.ReadErrorLog() | ? { $_.Text -match 'error' -OR $_.text -match 'Failed' -and $_text -notmatch "Error: 0x2098"}
You can iterate through each list and update $r accordingly, something like below should get you started.
Foreach($ExcludeText in $exclude){
$r = $r | ? {$_.text -notmatch $ExcludeText}
}
Foreach($IncludeText in $find){
$r = $r | ?{$_.text -match $IncludeText}
}
The above article has details on the undocumented TSQL version.
http://www.mssqltips.com/sqlservertip/1476/reading-the-sql-server-log-files-using-tsql/
I think you are missing some parameters. The xp_readerrorlog extended procedure takes the following parameters.
A - Number of error log file
B - 1 = SQL Server log, 2 - SQL Agent log
C - Search string 1
D - Search string 2
Here is a powershell solution like you asked.
I think the order of the and/or is important??
$srv = new-Object Microsoft.SqlServer.Management.Smo.Server("(local)")
$d = $srv.ReadErrorLog(0)
foreach ($r in $d.Rows)
{
if ( ($r['Text'] -match 'Error:' -and $r['Text'] -notmatch 'Error: 0x2098')
-or $r['Text'] -match 'Failed:' )
{
Write-Host "============================================"
Foreach ($c in $d.Columns)
{ Write-Host $c.ColumnName "=" $r[$c]}
}
}

How to get primary replica info for the SQL availability group by powershell

I want to get the machine of current primary replica in Powershell. Is there cmdlet to do it?
I load sqlps module and dump the find all sql command, but seems no one is related to this..
Thanks
I think this should work:
$null = [Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo");
$svr = New-Object Microsoft.SqlServer.Management.Smo.Server("AServerInstanceInYourAG");
$svr.AvailabilityGroups["YourAvailabilityGroup"].PrimaryReplicaServerName;
You can also get the Primary Replica by connecting to the listener
$null = [Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo");
$listenerName = "YourListenerName";
$sqlListener = New-Object Microsoft.SqlServer.Management.Smo.Server($listenerName);
$primaryReplicaName = $sqlListener.AvailabilityGroups.PrimaryReplicaServerName;
I use the owner name property from the cluster resource name:
get-clusterresource -name $AGName | Select -ExpandProperty OwnerNode
Below is an example I use to copy SQL agents from one node to another:
$Node1 = 'Node1\Instance1'
$Node2 = 'Node2\Instance2'
$AGName = "AG_Name"
$AGPrimary = get-clusterresource -name $AGName | Select -ExpandProperty OwnerNode
If ($AGPrimary -eq $env:COMPUTERNAME)
{
Copy-DbaAgentJob -source $Node1 -destination $Node2
write-host 'Success!'
}
else
{
Write-Output "No object copying is processed, this is not the primary node."
}
Although this is an old question, it is still useful almost 3 years later
Today, it is much easier to answer the question
import-module sqlserver;
$svr = "svr_name" # this can be any server involved in Availability Group
$AGPath = "sqlserver:\SQL\$svr\default\AvailabilityGroups";
dir -path $AGPath | select-object Name, PrimaryReplicaServerName;
This will return the AG name and the primary replica server name

Resources