diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/1_CreateJobCredentials.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/1_CreateJobCredentials.ps1 new file mode 100644 index 0000000..d3dbd83 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/1_CreateJobCredentials.ps1 @@ -0,0 +1,13 @@ +Write-Output "Creating job credentials..." +$jobAgent= Get-AzSqlElasticJobAgent -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -Name "sqlDemoAgent" +$LoginPasswordSecure = (ConvertTo-SecureString -String "password!123" -AsPlainText -Force) + +$MasterCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList "masteruser", $LoginPasswordSecure +$MasterCred = $JobAgent | New-AzSqlElasticJobCredential -Name "mastercred" -Credential $MasterCred + +$JobCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList "jobuser", $LoginPasswordSecure +$JobCred = $JobAgent | New-AzSqlElasticJobCredential -Name "jobcred" -Credential $JobCred + +$LoginPasswordSecure = (ConvertTo-SecureString -String "4IvufzC64R3z" -AsPlainText -Force) +$JobCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList "DBAdmin", $LoginPasswordSecure +$JobCred = $JobAgent | New-AzSqlElasticJobCredential -Name "dbadmin" -Credential $JobCred \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/2_createServerGroup.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/2_createServerGroup.ps1 new file mode 100644 index 0000000..db4b5a3 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/2_createServerGroup.ps1 @@ -0,0 +1,8 @@ +Write-Output "Creating test target groups..." +$jobAgent= Get-AzSqlElasticJobAgent -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -Name "sqlDemoAgent" +# Create ServerGroup target group +$TargetServerName="sqlagentdemo.database.windows.net" +$MasterCredName="mastercred" +$ServerGroup = $JobAgent | New-AzSqlElasticJobTargetGroup -Name 'ServerGroup1' +$ServerGroup | Add-AzSqlElasticJobTarget -ServerName $TargetServerName -RefreshCredentialName $MasterCredName +$ServerGroup | Add-AzSqlElasticJobTarget -ServerName $TargetServerName -Database "jobdatabase" -Exclude diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/3_CreateTableJob.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/3_CreateTableJob.ps1 new file mode 100644 index 0000000..a8a8f44 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/3_CreateTableJob.ps1 @@ -0,0 +1,13 @@ +$jobAgent= Get-AzSqlElasticJobAgent -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -Name "sqlDemoAgent" +$serverGroupName="ServerGroup1" +$credentialName="jobcred" + +Write-Output "Creating a new job" +$JobName = "CreateTablePS" +$Job = $JobAgent | New-AzSqlElasticJob -Name $JobName -RunOnce +$Job + + +Write-Output "Creating job steps" +$SqlText1 = "IF NOT EXISTS (SELECT * FROM sys.tables WHERE object_id = object_id('Step1Table')) CREATE TABLE [dbo].[Step1Table]([TestId] [int] NOT NULL);" +$Job | Add-AzSqlElasticJobStep -Name "step1" -TargetGroupName $serverGroupName -CredentialName $credentialName -CommandText $SqlText1 diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/4_startjob.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/4_startjob.ps1 new file mode 100644 index 0000000..a754a63 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/4_startjob.ps1 @@ -0,0 +1,10 @@ +$jobName="CreateTablePS" + +$jobexecution= Start-AzSqlElasticJob -JobName $jobName -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -AgentName "sqlDemoAgent" +$jobexecution + + +# $jobName="GetFragmentation" + +# $jobexecution= Start-AzSqlElasticJob -JobName $jobName -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -AgentName "sqlDemoAgent" +# $jobexecution \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/5_createOutputJob.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/5_createOutputJob.ps1 new file mode 100644 index 0000000..0b5ece3 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/5_createOutputJob.ps1 @@ -0,0 +1,36 @@ +$jobAgent= Get-AzSqlElasticJobAgent -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -Name "sqlDemoAgent" +$serverGroupName="ServerGroup1" +$credentialName="dbadmin" + +$outputDatebase=Get-AzSqlDatabase -DatabaseName "OutputDatabase" -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" + +Write-Output "Creating a new job" +$JobName = "GetFragmentation" +$Job = $JobAgent | New-AzSqlElasticJob -Name $JobName -RunOnce +$Job + + +Write-Output "Creating job steps" + + + +$SQLCommandString = @" +SELECT +DB_NAME() AS [Current Database], +SYSDATETIME() as [QueryDate], +dbschemas.[name] as 'Schema', +dbtables.[name] as 'Table', +dbindexes.[name] as 'Index', +indexstats.avg_fragmentation_in_percent, +indexstats.page_count +FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats +INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id] +INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id] +INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] +AND indexstats.index_id = dbindexes.index_id +WHERE indexstats.database_id = DB_ID() +ORDER BY indexstats.avg_fragmentation_in_percent desc +"@ + + +$Job | Add-AzSqlElasticJobStep -Name "step1" -TargetGroupName $serverGroupName -CredentialName $credentialName -CommandText $SQLCommandString -OutputDatabaseObject $outputDatebase -OutputCredentialName $credentialName -OutputTableName "Fragmentation4" -OutputSchemaName "dbo" diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/6_schedulJob.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/6_schedulJob.ps1 new file mode 100644 index 0000000..0700d90 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/6_schedulJob.ps1 @@ -0,0 +1,3 @@ +$JobName = "GetFragmentation" +$job= Get-AzSqlElasticJob -Name $jobName -ResourceGroupName "SQLAgentDemos" -ServerName "SQLAgentDemo" -AgentName "sqlDemoAgent" +$job | Set-AzSqlElasticJob -IntervalType Day -IntervalCount 1 -StartTime (Get-Date) -Enable \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/CreateDBUsers.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/CreateDBUsers.ps1 new file mode 100644 index 0000000..22e4b56 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/CreateDBUsers.ps1 @@ -0,0 +1,47 @@ +# In the master database (target server) +# - Create the master user login +# - Create the master user from master user login +# - Create the job user login +$TargetServer = Get-AzSqlServer -ResourceGroupName SQLAgentDemos -ServerName sqlagentdemo +$DB1=Get-AzSqlDatabase -ResourceGroupName SQLAgentDemos -DatabaseName "sqlAgentDemoDB01" -ServerName $TargetServer.ServerName +$DB2=Get-AzSqlDatabase -ResourceGroupName SQLAgentDemos -DatabaseName "sqlAgentDemoDB02" -ServerName $TargetServer.ServerName +$DB3=Get-AzSqlDatabase -ResourceGroupName SQLAgentDemos -DatabaseName "OutputDatabase" -ServerName $TargetServer.ServerName + +$AdminLogin="DBAdmin" +$AdminPassword="4IvufzC64R3z" + + +$Params = @{ + 'Database' = 'master' + 'ServerInstance' = $TargetServer.ServerName + '.database.windows.net' + 'Username' = $AdminLogin + 'Password' = $AdminPassword + 'OutputSqlErrors' = $true + 'Query' = "CREATE LOGIN masteruser WITH PASSWORD='password!123'" +} +Invoke-SqlCmd @Params +$Params.Query = "CREATE USER masteruser FROM LOGIN masteruser" +Invoke-SqlCmd @Params +$Params.Query = "CREATE LOGIN jobuser WITH PASSWORD='password!123'" +Invoke-SqlCmd @Params + +# For each of the target databases +# - Create the jobuser from jobuser login +# - Make sure they have the right permissions for successful script execution +$TargetDatabases = @($Db1.DatabaseName,$Db2.DatabaseName, $Db3.DatabaseName ) +$CreateJobUserScript = "CREATE USER jobuser FROM LOGIN jobuser" +$GrantAlterSchemaScript = "GRANT ALTER ON SCHEMA::dbo TO jobuser" +$GrantCreateScript = "GRANT CREATE TABLE TO jobuser" + +$TargetDatabases | % { + $Params.Database = $_ + + $Params.Query = $CreateJobUserScript + Invoke-SqlCmd @Params + + $Params.Query = $GrantAlterSchemaScript + Invoke-SqlCmd @Params + + $Params.Query = $GrantCreateScript + Invoke-SqlCmd @Params +} \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/cleanup.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/cleanup.ps1 new file mode 100644 index 0000000..8d4a37c --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/cleanup.ps1 @@ -0,0 +1,13 @@ +$resourceGroup="SQLAgentDemos" +$ServerName= "SQLAgentDemo" +$AgentName= "sqlDemoAgent" + +Get-AzSqlElasticJob -ResourceGroupName $resourceGroup -ServerName $ServerName -AgentName $AgentName | Remove-AzSqlElasticJob -Force + +Remove-AzSqlElasticJobTargetGroup -Name ServerGroup1 -ResourceGroupName $resourceGroup -ServerName $ServerName -AgentName $AgentName -Force + + +Remove-AzSqlElasticJobCredential -ResourceGroupName $resourceGroup -ServerName $ServerName -AgentName $AgentName -Name "mastercred" +Remove-AzSqlElasticJobCredential -ResourceGroupName $resourceGroup -ServerName $ServerName -AgentName $AgentName -Name "jobcred" +Remove-AzSqlElasticJobCredential -ResourceGroupName $resourceGroup -ServerName $ServerName -AgentName $AgentName -Name "dbadmin" + diff --git a/Sessions/Sam Cogan/ElasticJobs/PowerShell/createJobAgent.ps1 b/Sessions/Sam Cogan/ElasticJobs/PowerShell/createJobAgent.ps1 new file mode 100644 index 0000000..b1f52e5 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/PowerShell/createJobAgent.ps1 @@ -0,0 +1,4 @@ +Write-Output "Creating job agent..." +$AgentName = "sqlDemoAgent" +$JobDatabase = Get-AzSqlDatabase -ResourceGroupName "SQLAgentDemos" -ServerName "sqlagentdemo" -databasename "jobdatabase" +$JobAgent = $JobDatabase | New-AzSqlElasticJobAgent -Name $AgentName \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/SQL/CreateJobCredentials.sql b/Sessions/Sam Cogan/ElasticJobs/SQL/CreateJobCredentials.sql new file mode 100644 index 0000000..0d4edd0 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/SQL/CreateJobCredentials.sql @@ -0,0 +1,14 @@ +--Connect to the job database specified when creating the job agent + +-- Create a db master key if one does not already exist, using your own password. +CREATE MASTER KEY ENCRYPTION BY PASSWORD='GlxRvu27glE8tEIHny53'; + +-- Create a database scoped credential. +CREATE DATABASE SCOPED CREDENTIAL myjobcred WITH IDENTITY = 'jobuser', + SECRET = 'password!123'; +GO + +-- Create a database scoped credential for the master database of server1. +CREATE DATABASE SCOPED CREDENTIAL mymastercred WITH IDENTITY = 'masteruser', + SECRET = 'password!123'; +GO \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/SQL/createIndexRebuildJob.sql b/Sessions/Sam Cogan/ElasticJobs/SQL/createIndexRebuildJob.sql new file mode 100644 index 0000000..8a01890 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/SQL/createIndexRebuildJob.sql @@ -0,0 +1,13 @@ + + +--Connect to the job database specified when creating the job agent + +--Add job for create table +EXEC jobs.sp_add_job @job_name='RebuildAllIndexes', @description='Rebuild All Indexes' + +-- Add job step for create table +EXEC jobs.sp_add_jobstep @job_name='RebuildIndex', +@command=N'ALTER INDEX ALL ON Table_name +REBUILD ;', +@credential_name='myjobcred', +@target_group_name='ServerGroup1' \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/SQL/createNewTableJob.sql b/Sessions/Sam Cogan/ElasticJobs/SQL/createNewTableJob.sql new file mode 100644 index 0000000..5bcb426 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/SQL/createNewTableJob.sql @@ -0,0 +1,12 @@ +--Connect to the job database specified when creating the job agent + +--Add job for create table +EXEC jobs.sp_add_job @job_name='CreateTableTest', @description='Create Table Test' + +-- Add job step for create table +EXEC jobs.sp_add_jobstep @job_name='CreateTableTest', +@command=N'IF NOT EXISTS (SELECT * FROM sys.tables + WHERE object_id = object_id(''Test'')) +CREATE TABLE [dbo].[Test]([TestId] [int] NOT NULL);', +@credential_name='myjobcred', +@target_group_name='ServerGroup1' \ No newline at end of file diff --git a/Sessions/Sam Cogan/ElasticJobs/SQL/createServerGroup.sql b/Sessions/Sam Cogan/ElasticJobs/SQL/createServerGroup.sql new file mode 100644 index 0000000..b03ad18 --- /dev/null +++ b/Sessions/Sam Cogan/ElasticJobs/SQL/createServerGroup.sql @@ -0,0 +1,15 @@ +-- Connect to the job database specified when creating the job agent + +-- Add a target group containing server(s) +EXEC jobs.sp_add_target_group 'ServerGroup1' + +-- Add a server target member +EXEC jobs.sp_add_target_group_member +'ServerGroup1', +@target_type = 'SqlServer', +@refresh_credential_name='mymastercred', --credential required to refresh the databases in server +@server_name='sqlagentdemo.database.windows.net' + +--View the recently created target group and target group members +SELECT * FROM jobs.target_groups WHERE target_group_name='ServerGroup1'; +SELECT * FROM jobs.target_group_members WHERE target_group_name='ServerGroup1'; \ No newline at end of file diff --git a/Sessions/Sam Cogan/Functions/.vs/Functions/v15/.suo b/Sessions/Sam Cogan/Functions/.vs/Functions/v15/.suo new file mode 100644 index 0000000..ad49e7c Binary files /dev/null and b/Sessions/Sam Cogan/Functions/.vs/Functions/v15/.suo differ diff --git a/Sessions/Sam Cogan/Functions/.vs/VSWorkspaceState.json b/Sessions/Sam Cogan/Functions/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/Sessions/Sam Cogan/Functions/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/Sessions/Sam Cogan/Functions/.vs/slnx.sqlite b/Sessions/Sam Cogan/Functions/.vs/slnx.sqlite new file mode 100644 index 0000000..4e05049 Binary files /dev/null and b/Sessions/Sam Cogan/Functions/.vs/slnx.sqlite differ diff --git a/Sessions/Sam Cogan/Functions/HTTP.ps1 b/Sessions/Sam Cogan/Functions/HTTP.ps1 new file mode 100644 index 0000000..15e0ebd --- /dev/null +++ b/Sessions/Sam Cogan/Functions/HTTP.ps1 @@ -0,0 +1,177 @@ + + + param($request, $TriggerMetadata) + + $VerbosePreference="Continue" + + # # Get the stored username and password from the Automation credential + # $SqlCredential = Get-AutomationPSCredential -Name $SQLCredentialName +$SqlServerPort = 1433 +$RebuildOffline = $False + + # if ($SqlCredential -eq $null) + # { + # throw "Could not retrieve '$SQLCredentialName' credential asset. Check that you created this first in the Automation service." + # } + $SqlServer=[Environment]::GetEnvironmentVariable("SQLServer") + $Database=[Environment]::GetEnvironmentVariable("Database") + $SqlUsername =[Environment]::GetEnvironmentVariable("SQLCredentialUserName") + $SqlPass = [Environment]::GetEnvironmentVariable("SQLCredentialPassword") + write-output "Fragmentation Level = $REQ_QUERY_FragPercentage" + if($REQ_QUERY_FragPercentage){ + $FragPercentage = $REQ_QUERY_FragPercentage + } + else{ + $FragPercentage=20 + } + + $TableNames=@() + + # Define the connection to the SQL Database + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # SQL command to find tables and their average fragmentation + $SQLCommandString = @" + SELECT a.object_id, avg_fragmentation_in_percent + FROM sys.dm_db_index_physical_stats ( + DB_ID(N'$Database') + , OBJECT_ID(0) + , NULL + , NULL + , NULL) AS a + JOIN sys.indexes AS b + ON a.object_id = b.object_id AND a.index_id = b.index_id; +"@ + # Return the tables with their corresponding average fragmentation + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $FragmentedTable=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($FragmentedTable) + + + # Get the list of tables with their object ids + $SQLCommandString = @" + SELECT t.name AS TableName, t.OBJECT_ID FROM sys.tables t +"@ + + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $TableSchema =New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($TableSchema) + + + # Return the table names that have high fragmentation + ForEach ($FragTable in $FragmentedTable.Tables[0]) + { + Write-Output ("Table Object ID:" + $FragTable.Item("object_id")) + Write-Output ("Fragmentation:" + $FragTable.Item("avg_fragmentation_in_percent")) + + If ($FragTable.avg_fragmentation_in_percent -ge $FragPercentage) + { + # Table is fragmented. Return this table for indexing by finding its name + ForEach($Id in $TableSchema.Tables[0]) + { + if ($Id.OBJECT_ID -eq $FragTable.object_id.ToString()) + { + # Found the table name for this table object id. Return it + Write-Output ("Found a table to index! : " + $Id.Item("TableName")) + $TableNames+=$Id.TableName + } + } + } + } + + $Conn.Close() + + + # If a specific table was specified, then find this table if it needs to indexed, otherwise + # set the TableNames to $null since we shouldn't process any other tables. + If ($Table) + { + Write-Output ("Single Table specified: $Table") + If ($TableNames -contains $Table) + { + $TableNames = $Table + } + Else + { + # Remove other tables since only a specific table was specified. + Write-Output ("Table not found: $Table") + $TableNames = $Null + } + } + + # Interate through tables with high fragmentation and rebuild indexes + ForEach ($TableName in $TableNames) + { + + + Write-Output "Indexing Table $TableName..." + + + + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD with (ONLINE=ON)') +"@ + + # Define the connection to the SQL Database + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # Define the SQL command to run. In this case we are getting the number of rows in the table + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + Try + { + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Catch + { + if (($_.Exception -match "offline") -and ($RebuildOffline) ) + { + Write-Output ("Building table $TableName offline") + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD') +"@ + + # Define the SQL command to run. + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Else + { + # Will catch the exception here so other tables can be processed. + Write-Error "Table $TableName could not be indexed. Investigate indexing each index instead of the complete table $_" + } + } + # Close the SQL connection + $Conn.Close() + } + + + Write-Output "Finished Indexing" + diff --git a/Sessions/Sam Cogan/Functions/MSI.ps1 b/Sessions/Sam Cogan/Functions/MSI.ps1 new file mode 100644 index 0000000..000e768 --- /dev/null +++ b/Sessions/Sam Cogan/Functions/MSI.ps1 @@ -0,0 +1,203 @@ + param($request, $TriggerMetadata) + + $VerbosePreference="Continue" + + # # Get the stored username and password from the Automation credential + # $SqlCredential = Get-AutomationPSCredential -Name $SQLCredentialName +$SqlServerPort = 1433 +$RebuildOffline = $False + +if ($env:MSI_SECRET -and (Get-Module -ListAvailable Az.Accounts)) { + Connect-AzAccount -Identity +} + +Function Get-AzureFunctionKeyVaultSecret { +[CmdletBinding()] + +param ( +#The full URI to the key, you can copy and paste this from your Key properties in the Azure Portal +[Parameter(Mandatory)]$SecretIdentifier +) + + +} + + + +$VerbosePreference="Continue" + +# # Get the stored username and password from the Automation credential +# $SqlCredential = Get-AutomationPSCredential -Name $SQLCredentialName + + + +# if ($SqlCredential -eq $null) +# { +# throw "Could not retrieve '$SQLCredentialName' credential asset. Check that you created this first in the Automation service." +# } +$SqlServer=[Environment]::GetEnvironmentVariable("SQLServer") +$Database=[Environment]::GetEnvironmentVariable("Database") +$SqlUsername =$(get-azkeyvaultsecret -VaultName 'sqlagentdemokv' -Name "sqlusername").secretText +$SqlPass =$(get-azkeyvaultsecret -VaultName 'sqlagentdemokv' -Name "sqlpassword").secretText + +write-output "username $SqlUsername" +write-output "Fragmentation Level = $REQ_QUERY_FragPercentage" +if($REQ_QUERY_FragPercentage){ + $FragPercentage = $REQ_QUERY_FragPercentage +} +else{ + $FragPercentage=20 +} + +$TableNames=@() + + # Define the connection to the SQL Database + write-output "Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # SQL command to find tables and their average fragmentation + $SQLCommandString = @" + SELECT a.object_id, avg_fragmentation_in_percent + FROM sys.dm_db_index_physical_stats ( + DB_ID(N'$Database') + , OBJECT_ID(0) + , NULL + , NULL + , NULL) AS a + JOIN sys.indexes AS b + ON a.object_id = b.object_id AND a.index_id = b.index_id; +"@ + # Return the tables with their corresponding average fragmentation + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $FragmentedTable=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($FragmentedTable) + + + # Get the list of tables with their object ids + $SQLCommandString = @" + SELECT t.name AS TableName, t.OBJECT_ID FROM sys.tables t +"@ + + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $TableSchema =New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($TableSchema) + + + # Return the table names that have high fragmentation + ForEach ($FragTable in $FragmentedTable.Tables[0]) + { + Write-Output ("Table Object ID:" + $FragTable.Item("object_id")) + Write-Output ("Fragmentation:" + $FragTable.Item("avg_fragmentation_in_percent")) + + If ($FragTable.avg_fragmentation_in_percent -ge $FragPercentage) + { + # Table is fragmented. Return this table for indexing by finding its name + ForEach($Id in $TableSchema.Tables[0]) + { + if ($Id.OBJECT_ID -eq $FragTable.object_id.ToString()) + { + # Found the table name for this table object id. Return it + Write-Output ("Found a table to index! : " + $Id.Item("TableName")) + $TableNames+=$Id.TableName + } + } + } + } + + $Conn.Close() + + +# If a specific table was specified, then find this table if it needs to indexed, otherwise +# set the TableNames to $null since we shouldn't process any other tables. +If ($Table) +{ + Write-Output ("Single Table specified: $Table") + If ($TableNames -contains $Table) + { + $TableNames = $Table + } + Else + { + # Remove other tables since only a specific table was specified. + Write-Output ("Table not found: $Table") + $TableNames = $Null + } +} + +# Interate through tables with high fragmentation and rebuild indexes +ForEach ($TableName in $TableNames) +{ + + +Write-Output "Indexing Table $TableName..." + + + + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD with (ONLINE=ON)') +"@ + + # Define the connection to the SQL Database + write-host "Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # Define the SQL command to run. In this case we are getting the number of rows in the table + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + Try + { + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Catch + { + if (($_.Exception -match "offline") -and ($RebuildOffline) ) + { + Write-Output ("Building table $TableName offline") + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD') +"@ + + # Define the SQL command to run. + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Else + { + # Will catch the exception here so other tables can be processed. + Write-Error "Table $TableName could not be indexed. Investigate indexing each index instead of the complete table $_" + } + } + # Close the SQL connection + $Conn.Close() +} + + +Write-Output "Finished Indexing" + diff --git a/Sessions/Sam Cogan/Functions/TableStorage.ps1 b/Sessions/Sam Cogan/Functions/TableStorage.ps1 new file mode 100644 index 0000000..2870b6e --- /dev/null +++ b/Sessions/Sam Cogan/Functions/TableStorage.ps1 @@ -0,0 +1,185 @@ + + + param($request,$inputValues, $TriggerMetadata) + + $VerbosePreference="Continue" + +$SqlServerPort = 1433 +$RebuildOffline = $False + + $VerbosePreference="Continue" + + # # Get the stored username and password from the Automation credential + # $SqlCredential = Get-AutomationPSCredential -Name $SQLCredentialName + +write-output "input:" + $($inputValues | out-string) + + write-output "username: $($inputValues.SQLCredentialUsername)" + + + + # if ($SqlCredential -eq $null) + # { + # throw "Could not retrieve '$SQLCredentialName' credential asset. Check that you created this first in the Automation service." + # } + $SqlServer= $inputValues.SQLServer + $Database= $inputValues.Database + $SqlUsername = $inputValues.SQLCredentialUsername + $SqlPass = $inputValues.SQLCredentialPassword + write-output "Fragmentation Level = $REQ_QUERY_FragPercentage" + if($REQ_QUERY_FragPercentage){ + $FragPercentage = $REQ_QUERY_FragPercentage + } + else{ + $FragPercentage=20 + } + + $TableNames=@() + write-output "Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" + # Define the connection to the SQL Database + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # SQL command to find tables and their average fragmentation + $SQLCommandString = @" + SELECT a.object_id, avg_fragmentation_in_percent + FROM sys.dm_db_index_physical_stats ( + DB_ID(N'$Database') + , OBJECT_ID(0) + , NULL + , NULL + , NULL) AS a + JOIN sys.indexes AS b + ON a.object_id = b.object_id AND a.index_id = b.index_id; +"@ + # Return the tables with their corresponding average fragmentation + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $FragmentedTable=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($FragmentedTable) + + + # Get the list of tables with their object ids + $SQLCommandString = @" + SELECT t.name AS TableName, t.OBJECT_ID FROM sys.tables t +"@ + + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + $Cmd.CommandTimeout=120 + + # Execute the SQL command + $TableSchema =New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($TableSchema) + + + # Return the table names that have high fragmentation + ForEach ($FragTable in $FragmentedTable.Tables[0]) + { + Write-Output ("Table Object ID:" + $FragTable.Item("object_id")) + Write-Output ("Fragmentation:" + $FragTable.Item("avg_fragmentation_in_percent")) + + If ($FragTable.avg_fragmentation_in_percent -ge $FragPercentage) + { + # Table is fragmented. Return this table for indexing by finding its name + ForEach($Id in $TableSchema.Tables[0]) + { + if ($Id.OBJECT_ID -eq $FragTable.object_id.ToString()) + { + # Found the table name for this table object id. Return it + Write-Output ("Found a table to index! : " + $Id.Item("TableName")) + $TableNames+=$Id.TableName + } + } + } + } + + $Conn.Close() + + + # If a specific table was specified, then find this table if it needs to indexed, otherwise + # set the TableNames to $null since we shouldn't process any other tables. + If ($Table) + { + Write-Output ("Single Table specified: $Table") + If ($TableNames -contains $Table) + { + $TableNames = $Table + } + Else + { + # Remove other tables since only a specific table was specified. + Write-Output ("Table not found: $Table") + $TableNames = $Null + } + } + + # Interate through tables with high fragmentation and rebuild indexes + ForEach ($TableName in $TableNames) + { + + + Write-Output "Indexing Table $TableName..." + + + + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD with (ONLINE=ON)') +"@ + + # Define the connection to the SQL Database + $Conn = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$SqlServer,$SqlServerPort;Database=$Database;User ID=$SqlUsername;Password=$SqlPass;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;") + + # Open the SQL connection + $Conn.Open() + + # Define the SQL command to run. In this case we are getting the number of rows in the table + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + Try + { + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Catch + { + if (($_.Exception -match "offline") -and ($RebuildOffline) ) + { + Write-Output ("Building table $TableName offline") + $SQLCommandString = @" + EXEC('ALTER INDEX ALL ON $TableName REBUILD') +"@ + + # Define the SQL command to run. + $Cmd=new-object system.Data.SqlClient.SqlCommand($SQLCommandString, $Conn) + # Set the Timeout to be less than 30 minutes since the job will get queued if > 30 + # Setting to 25 minutes to be safe. + $Cmd.CommandTimeout=1500 + + # Execute the SQL command + $Ds=New-Object system.Data.DataSet + $Da=New-Object system.Data.SqlClient.SqlDataAdapter($Cmd) + [void]$Da.fill($Ds) + } + Else + { + # Will catch the exception here so other tables can be processed. + Write-Error "Table $TableName could not be indexed. Investigate indexing each index instead of the complete table $_" + } + } + # Close the SQL connection + $Conn.Close() + } + + + Write-Output "Finished Indexing" diff --git a/Sessions/Sam Cogan/SQLAgent_in_the_cloud.pptx b/Sessions/Sam Cogan/SQLAgent_in_the_cloud.pptx new file mode 100644 index 0000000..a8bc259 Binary files /dev/null and b/Sessions/Sam Cogan/SQLAgent_in_the_cloud.pptx differ