Replace backup location in SQL jobs - sql-server

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.

Related

How to implement Where-Object in SQL-Invoke command

I am trying to filter some tables from my database which exists in my solution folder. I want to filter all tables that I am pulling from SQL Server:
$existingTables = "Table1", "Table2", "Table3", "Table4"
#getting all tables except existing ones
#SqlQuery = "SELECT name FROM sys.Tables order by name asc"
$filteredTables = ((Invoke-SQL -DataSource $ServerName -DatabaseName $DatabaseName -UserID $UserID -Password $Password -SqlCommand $SQLQuery).name | ? {$_ -notcontains $existingTables})
#$filteredTables returns all tables, including the existing ones...
I've tried $_.name and it is the same result.
You're using the operands of the -notcontains operator in the wrong order. The correct syntax is
reference_array -notcontains item
In your case:
$existingTables -notcontains $_
or
$existingTables -notcontains $_.Name
if you don't expand the property Name.
If you want to use the reference array as the second operand you must use the -notin operator:
$_ -notin $existingTables
However, that operator is not available prior to PowerShell v3.
Alternatively, you could add an exclude clause to your SQL statement, as #vonPryz suggested in the comments. Take care to not open yourself to SQL injection when doing that, though.
Don't do this:
$SQLQuery = #"
SELECT ...
FROM ...
WHERE name NOT IN ('$($existingTables[0])', '$($existingTables[1])', ...)
"#
Use a prepared statement (or "parameterized query" as Microsoft calls them). I don't recognize the cmdlet you're using, though (doesn't seem to be Invoke-Sqlcmd), so I can't tell you how to do it with that cmdlet.

Powershell script scripts on dbachecks to compare the MaxMemory of server listed in a table

Run checks against servers
Import-Module dbatools
Import-Module dbachecks
$Server = "AMCB123"
$Database = "DBA"
# Create recordset of servers to evaluate
$sconn = new-object System.Data.SqlClient.SqlConnection("server=$Server;Trusted_Connection=true");
$q = "SELECT DISTINCT servername FROM DBA.[dbo].[Server_Group] WHERE ID =1;"
$sconn.Open()
$cmd = new-object System.Data.SqlClient.SqlCommand ($q, $sconn);
$cmd.CommandTimeout = 0;
$dr = $cmd.ExecuteReader();
# Loop through the servers and build an array
while ($dr.Read()) {
Get-DbaMaxMemory -SqlServer $dr.GetValue(0) | Format-Table
}
$dr.Close()
$sconn.Close()
I have Listed the sql server(stage, prod, DR servers in a table as per the groups), Now I want to compare the servers with group id's to check wethere the servers(stage,prod, DR) with same group id is having same MAXMemory cofiguration or not.
For this I'm using the below powershell script can you please help me with this, I have created a table with all the servewith grop id.
Request to please help me with the loop thorugh the servers and build an array, so that I can run the MAXMEMORY powershell command to compare it using the group id for all servers.
I have collected all the servers details into a table dbo.server groups
the powershell script should iterate through the table by using the ID and check whether the servers in the ID group has same MAXMEMORY configuration ID server_name Environment
1 ABC0123 prod
1 ABC5123 stage
1 ABC4123 DR
2 DEF0123 prod
2 DEF5123 stage
2 DEF4123 DR
I'm trying to use a powershell script which will check and compare the MAXMEMORY configuration as per the ID(to check whether stage, prod, DR server of the same group_id have similar setting or not), if not then it will display a warning/message as group_ids servers are not configured similarly.
Please help me with the script
You're making this script longer than it needs to be. Also, you're using Format-Table prematurely - you should only use the Format-* functions for displaying final information to the user; they output strings, not properly typed data/variables that can be used down the line.
Use the tools that PowerShell and dbatools give you to get your server list, and then pass that list to Get-DbaMaxMemory as a collection.
import-module dbatools
$ServerList = Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1" | Select-Object -ExpandProperty servername;
Get-DbaMaxMemory -ServerInstance $ServerList | Select-Object SqlInstance, SqlMaxMB;
This will give you a list of your SQL instances and the memory they're configured to use. What you do after that...it's hard to say as you haven't clearly defined what you're looking for.
But this may not tell the full story. Wouldn't it be better to check the configured values and what you're currently running with? You can do that with Get-DbaSpConfigure.
import-module dbatools
$ServerList = Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1" | Select-Object -ExpandProperty servername;
Get-DbaSpConfigure -ServerInstance $ServerList | Select-Object ServerName,ConfiguredValue,RunningValue;
You can even create a computed column in that final Select-Object to tell you if the configured & running values differ.
If you just wanted to use dbachecks (which uses dbatools in the background) you can use
$ServerList = (Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1").servername
and
Invoke-DbcCheck -SQlInstance $ServerList -Check MaxMemory
Or you can set the configuration item app.computername and app.sqlinstance to your server list using
Set-DbcConfig -Name app.sqlinstance -Value $serverlist
Set-DbcConfig -Name app.computername -Value $serverlist
and then you can run this (or any other checks) using
Invoke-DbcCheck -Check MaxMemory

Powershell: Find all SQL Server instances per ser 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.

need help to add worksheet to existing xlsx via PowerShell module "PSExcel"

I'm working on a project to create some Excel reports with data from MSSQL databases and need some help with the end result.
Disclaimer - I'm not great with PowerShell and MSSQL).
So far, with the help of the internet, I've managed to create a .ps1 file that elevates itself, imports a PS module called PSExcel (https://github.com/RamblingCookieMonster/PSExcel), imports the SQLPS module and runs three separate queries to export to three separate .xlsx files.
My script for the last few parts is:
# Import PSExcel module
Import-Module C:\scripts-and-reports\Modules\PSExcel
# Import SQLPS module
Import-Module sqlps
# Import PSExcel module
Import-Module C:\scripts-and-reports\Modules\PSExcel
# Import SQLPS module
Import-Module sqlps
# Create individuals
invoke-sqlcmd -inputfile "C:\scripts-and-reports\individuals.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Individuals" -Path "C:\scripts-and-reports\Individuals.xlsx" -Table -Force
# Create joint parties
invoke-sqlcmd -inputfile "C:\scripts-and-reports\joint-parties.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Joint Parties" -Path "C:\scripts-and-reports\Joint Parties.xlsx" -Table -Force
# Create organisations
invoke-sqlcmd -inputfile "C:\scripts-and-reports\organisations.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Organisations" -Path "C:\scripts-and-reports\Organisations.xlsx" -Table -Force
I've tried to no avail to combine the last two query exports into the first query's export as additional worksheets so that I only have a single Excel workbook to hand to my boss, but I think I must be approaching it incorrectly.
When I read the example on Line 94 of ../Export-XLSX.ps1 and try to implement it in my scenario by changing the file names to match each other, the last query replaces the first in the outputted .xlsx file. This must be because of the -Force. Changing that to -Append won't help because then it says the file already exists.
Can anyone help me by first showing me where I'm going wrong and then pointing me in the right direction (this may end up in a walkthrough).
Please and thanks!
---** UPDATE **---
With #gms0ulman's fix to Export-XLSX.ps1, it looks like it's going to work as I've tested it with different SQL queries to what I need and it adds the worksheets OK.
The queries I'm using for the test are
SELECT DISTINCT
case mt.[Status]
when 0 then 'In Progress'
When 1 then 'On Hold'
when 2 then 'Completed'
when 3 then 'Not Proceeding'
else 'Unknown' end as MatterStatus,
mt.LastUpdatedOn as LastModified
from matter mt
where mt.LastUpdatedOn >= '2016-07-01'
AND (mt.[status] = 0 or mt.[status] = 1)
While this (and the other two iterations of it in my PS script) works, my actual queries don't. The queries themselves work, and the first export too, but when -Append is used in PS with the invoke-sqlcmd and invoke-sqlcmd -inputfile "query2.sql" -serverinstance "" -database "" | Export-XLSX -WorksheetName "Joint Parties" -Path "C:\scripts-and-reports\Matter Details.xlsx" -Table -Append, the two appended worksheets get the error:
Exceptions calling "SaveAs" with "1" argument(s): "Error saving file C:\scripts-and-reports\Matter Details.xlsx"
At C:\scripts-and-reports\Modules\PSExcel\Export-XLSX.ps1:496 char:13
$Excel.SaveAs($Path)
~~~~~~~~~~~~~~~~~~~~
CategoryInfo : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : InvalidOperationException
The problem is with the module you're using. -Append should work. In the Export-XLSX.ps1 file, look at lines 351 and 371.
As you are providing an existing path, 351 evaluates to true and 371 never gets executed.
Line 371 is where the module creates a new worksheet for you.
if (($Append -or $ClearSheet) -and ($PSBoundParameters.ContainsKey('Excel') -or (Test-Path $Path)) ) # line 351
{
$WorkSheet=$Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName}
if($ClearSheet)
{
$WorkSheet.Cells[$WorkSheet.Dimension.Start.Row, $WorkSheet.Dimension.Start.Column, $WorkSheet.Dimension.End.Row, $WorkSheet.Dimension.End.Column].Clear()
}
if($Append)
{
$RealHeaderCount = $WorkSheet.Dimension.Columns
if($Header.count -ne $RealHeaderCount)
{
$Excel.Dispose()
Throw "Found $RealHeaderCount existing headers, provided data has $($Header.count)."
}
$RowIndex = 1 + $Worksheet.Dimension.Rows
}
}
else
{
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName) #line 371
}
To get around this, I've added a couple of lines to the Export-XLSX.ps1 file.
Now, even if the path exists, it will:
check if a $WorksheetName is provided
check this worksheet does not already exist
create new worksheet if both are true.
Note you will have to Remove-Module and Import-Module for changes to be recognised. You will need to use -Append. I used -Force on first worksheet, not the second one. Also, you may find -Verbose helpful as it provides you with more information as the script runs - good for debugging.
if (($Append -or $ClearSheet) -and ($PSBoundParameters.ContainsKey('Excel') -or (Test-Path $Path)) )
{
# New line: even if path exists, check for $WorkSheetName and that this worksheet doesn't exist
if($WorkSheetName -and $Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName} -eq $null)
{
# if you have $WorksheetName and it doesn't exist, create worksheet.
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName)
}
else
{
$WorkSheet=$Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName}
if($ClearSheet)
{
$WorkSheet.Cells[$WorkSheet.Dimension.Start.Row, $WorkSheet.Dimension.Start.Column, $WorkSheet.Dimension.End.Row, $WorkSheet.Dimension.End.Column].Clear()
}
if($Append)
{
$RealHeaderCount = $WorkSheet.Dimension.Columns
if($Header.count -ne $RealHeaderCount)
{
$Excel.Dispose()
Throw "Found $RealHeaderCount existing headers, provided data has $($Header.count)."
}
$RowIndex = 1 + $Worksheet.Dimension.Rows
}
} # this is also new.
}
else
{
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName)
}

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]}
}
}

Resources