├── AlwaysOn
├── 00_setup_cluster.ps1
├── 01_setup_instances.ps1
├── 01_setup_instances_SQL2016.ps1
├── 01_setup_instances_SQL2017.ps1
├── 02_setup_availability_group.ps1
├── 02_setup_availability_group_SQL2016.ps1
├── 02_setup_availability_group_SQL2017.ps1
├── 03_add_SQL03_to_Cluster_and_AvailabilityGroup.ps1
├── 99_remove.ps1
└── README.md
├── AlwaysOn_DatabaseOwner
└── 01_database_owner.ps1
├── AlwaysOn_MultiInstance
├── 00_setup_cluster.ps1
├── 01_setup_instances.ps1
├── 02_setup_availability_group.ps1
├── 02_setup_availability_group_demo_01_AgHadr_Endpoint_XESession.ps1
├── 02_setup_availability_group_demo_02_empty_availability_group.ps1
├── 02_setup_availability_group_demo_03a_add_database_with_manual_seeding.ps1
├── 02_setup_availability_group_demo_03b_add_database_with_automatic_seeding.ps1
└── 03_add_SRV3_to_Cluster_and_AvailabilityGroup.ps1
├── AutomatedLab
├── 01_Install_HyperV.ps1
├── 02_Install_AutomatedLab.ps1
├── 03_Setup_TestLab.ps1
├── 04_Download_CustomAssets.ps1
├── CustomAssets
│ ├── DockerImages
│ │ └── README.md
│ ├── FileServerISOs
│ │ └── README.md
│ ├── GPOs
│ │ ├── GPO_COMP_CredSSP_Server
│ │ │ ├── manifest.xml
│ │ │ └── {A30D2B62-F106-40AD-9C6F-6E410751F850}
│ │ │ │ ├── Backup.xml
│ │ │ │ ├── DomainSysvol
│ │ │ │ └── GPO
│ │ │ │ │ └── Machine
│ │ │ │ │ ├── comment.cmtx
│ │ │ │ │ └── registry.pol
│ │ │ │ ├── bkupInfo.xml
│ │ │ │ └── gpreport.xml
│ │ ├── GPO_COMP_PasswordAge_0
│ │ │ ├── manifest.xml
│ │ │ └── {9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}
│ │ │ │ ├── Backup.xml
│ │ │ │ ├── DomainSysvol
│ │ │ │ └── GPO
│ │ │ │ │ └── Machine
│ │ │ │ │ └── microsoft
│ │ │ │ │ └── windows nt
│ │ │ │ │ └── SecEdit
│ │ │ │ │ └── GptTmpl.inf
│ │ │ │ ├── bkupInfo.xml
│ │ │ │ └── gpreport.xml
│ │ ├── GPO_COMP_PowerPlan_HighPerformance
│ │ │ ├── manifest.xml
│ │ │ └── {B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}
│ │ │ │ ├── Backup.xml
│ │ │ │ ├── DomainSysvol
│ │ │ │ └── GPO
│ │ │ │ │ └── Machine
│ │ │ │ │ ├── comment.cmtx
│ │ │ │ │ └── registry.pol
│ │ │ │ ├── bkupInfo.xml
│ │ │ │ └── gpreport.xml
│ │ ├── GPO_USR_DriveMapping_F_FileServer
│ │ │ ├── manifest.xml
│ │ │ └── {5CB66590-4C6F-4196-AC68-C9F028B5A7CB}
│ │ │ │ ├── Backup.xml
│ │ │ │ ├── DomainSysvol
│ │ │ │ └── GPO
│ │ │ │ │ └── User
│ │ │ │ │ └── Preferences
│ │ │ │ │ └── Drives
│ │ │ │ │ └── Drives.xml
│ │ │ │ ├── bkupInfo.xml
│ │ │ │ └── gpreport.xml
│ │ └── GPO_USR_Explorer
│ │ │ ├── manifest.xml
│ │ │ └── {DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}
│ │ │ ├── Backup.xml
│ │ │ ├── DomainSysvol
│ │ │ └── GPO
│ │ │ │ └── User
│ │ │ │ └── Preferences
│ │ │ │ └── Registry
│ │ │ │ └── Registry.xml
│ │ │ ├── bkupInfo.xml
│ │ │ └── gpreport.xml
│ ├── SampleDatabases
│ │ └── README.md
│ └── Software
│ │ └── README.md
├── CustomScripts
│ ├── Docker_Databases.ps1
│ ├── README.md
│ ├── SQLServer_AlwaysOn.ps1
│ ├── Win2022_Oracle.ps1
│ └── Win2022_SQLServer.ps1
└── README.md
├── LICENSE
├── PowerShell
└── ConvertTo-Json.ipynb
├── README.md
├── SQLServer
├── Fragmentation.sql
└── Fragmentation_createDemoData.sql
├── dbatools
├── Get-CU.ps1
├── Test_Experimental_Connect-DbaInstance.ps1
└── downgrade_database.ps1
└── sp_Blitz_markdown
├── README.md
└── sp_Blitz_output_as_markdown.ps1
/AlwaysOn/00_setup_cluster.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
6 | [string]$ClusterName = 'CLUSTER1',
7 | [string]$ClusterIP = '192.168.3.70'
8 | )
9 |
10 | $ErrorActionPreference = 'Stop'
11 |
12 | Import-Module -Name PSFramework
13 |
14 | try {
15 |
16 | Write-PSFMessage -Level Host -Message 'Install cluster feature on each node'
17 | Invoke-Command -ComputerName $ClusterNodes -ScriptBlock { Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools } | Format-Table
18 |
19 | Write-PSFMessage -Level Host -Message 'Install cluster admin tools on local computer'
20 | Install-WindowsFeature -Name RSAT-Clustering | Format-Table
21 |
22 | Write-PSFMessage -Level Host -Message 'Run cluster test'
23 | $clusterTest = Test-Cluster -Node $ClusterNodes
24 | Write-PSFMessage -Level Host -Message "Result of cluster test is available at: $($clusterTest.FullName)"
25 | <# To display result in web browser run:
26 | & $clusterTest.FullName
27 | #>
28 |
29 | Write-PSFMessage -Level Host -Message 'Create the cluster'
30 | $null = New-Cluster -Name $ClusterName -Node $ClusterNodes -StaticAddress $ClusterIP
31 |
32 | Write-PSFMessage -Level Host -Message 'Create a share as cluster quorum and configure the cluster'
33 | Invoke-Command -ComputerName $DomainController -ScriptBlock {
34 | $null = New-Item -Path "C:\WindowsClusterQuorum_$using:ClusterName" -ItemType Directory
35 | $null = New-SmbShare -Path "C:\WindowsClusterQuorum_$using:ClusterName" -Name "WindowsClusterQuorum_$using:ClusterName"
36 | $null = Grant-SmbShareAccess -Name "WindowsClusterQuorum_$using:ClusterName" -AccountName "$using:DomainName\$using:ClusterName$" -AccessRight Full -Force
37 | }
38 | Set-ClusterQuorum -Cluster $ClusterName -NodeAndFileShareMajority "\\$DomainController\WindowsClusterQuorum_$ClusterName" | Format-List
39 |
40 | Write-PSFMessage -Level Host -Message 'Grant necessary rights to the computer account of the cluster'
41 | $adComputerGUID = [GUID]::new('bf967a86-0de6-11d0-a285-00aa003049e2')
42 | $adClusterComputer = Get-ADComputer -Filter "Name -eq '$ClusterName'"
43 | $adClusterIdentity = [System.Security.Principal.SecurityIdentifier]::new($adClusterComputer.SID)
44 | $adClusterOU = [ADSI]([ADSI]"LDAP://$($adClusterComputer.DistinguishedName)").Parent
45 | $accessRule1 = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($adClusterIdentity, "ReadProperty", "Allow", [GUID]::Empty, "All", [GUID]::Empty)
46 | $accessRule2 = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($adClusterIdentity, "CreateChild", "Allow", $adComputerGUID, "All", [GUID]::Empty)
47 | $adClusterOU.psbase.ObjectSecurity.AddAccessRule($accessRule1)
48 | $adClusterOU.psbase.ObjectSecurity.AddAccessRule($accessRule2)
49 | $adClusterOU.psbase.CommitChanges()
50 |
51 | Write-PSFMessage -Level Host -Message 'finished'
52 |
53 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
54 |
--------------------------------------------------------------------------------
/AlwaysOn/01_setup_instances.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
6 | [string[]]$SqlInstances = @('SQL01', 'SQL02'),
7 | [string]$SQLServerServiceAccount = 'gMSA-SQLServer',
8 | [SecureString]$AdminPassword = (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force),
9 | [string]$SQLServerSourcesPath = '\\fs\Software\SQLServer\ISO',
10 | [string]$SQLServerPatchesPath = '\\fs\Software\SQLServer\CU',
11 | [string]$SampleDatabasesPath = '\\fs\SampleDatabases',
12 | [string]$DatabaseName = 'AdventureWorks'
13 | )
14 |
15 | $ErrorActionPreference = 'Stop'
16 |
17 | Import-Module -Name PSFramework
18 | Import-Module -Name dbatools
19 |
20 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
21 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
22 |
23 | try {
24 |
25 | $administratorCredential = [PSCredential]::new("$DomainName\Admin", $AdminPassword)
26 | $sqlServerCredential = [PSCredential]::new("$DomainName\$SQLServerServiceAccount$", [SecureString]::new())
27 |
28 | if ($null -eq (Get-ADServiceAccount -Filter "Name -eq '$SQLServerServiceAccount'") ) {
29 | throw "Service Account '$SQLServerServiceAccount' for SQL Server not found. Creation of this account is not part of this script but done in https://github.com/andreasjordan/demos/blob/master/AutomatedLab/CustomScripts/SQLServer_AlwaysOn.ps1"
30 | }
31 |
32 | Write-PSFMessage -Level Host -Message 'Change powerplan of cluster nodes to high performance'
33 | Set-DbaPowerPlan -ComputerName $ClusterNodes | Format-Table
34 |
35 | Write-PSFMessage -Level Host -Message 'Install SQL Server instances on cluster nodes'
36 | $installParams = @{
37 | SqlInstance = $SqlInstances
38 | Version = 2022
39 | Feature = 'Engine'
40 | Path = $SQLServerSourcesPath
41 | UpdateSourcePath = $SQLServerPatchesPath
42 | EngineCredential = $sqlServerCredential
43 | AgentCredential = $sqlServerCredential
44 | AuthenticationMode = 'Mixed'
45 | Credential = $administratorCredential
46 | Restart = $true
47 | EnableException = $false
48 | }
49 | $installResult = @( )
50 | $installResult += Install-DbaInstance @installParams
51 | $installResult | Format-Table
52 | if ($installResult.Count -lt $SqlInstances.Count -or $false -in $installResult.Successful) {
53 | throw "Install-DbaInstance not successful"
54 | }
55 |
56 | Write-PSFMessage -Level Host -Message 'Grant instant file initialization rights to SQL Server service account on cluster nodes'
57 | Set-DbaPrivilege -ComputerName $ClusterNodes -Type IFI
58 |
59 | Write-PSFMessage -Level Host -Message 'Add rule to firewall on cluster nodes'
60 | New-DbaFirewallRule -SqlInstance $SqlInstances | Format-Table
61 |
62 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instances: MaxMemory / MaxDop / CostThresholdForParallelism'
63 | Set-DbaMaxMemory -SqlInstance $SqlInstances -Max 2048 | Format-Table
64 | Set-DbaMaxDop -SqlInstance $SqlInstances | Format-Table
65 | Set-DbaSpConfigure -SqlInstance $SqlInstances -Name CostThresholdForParallelism -Value 50 | Format-Table
66 |
67 | Write-PSFMessage -Level Host -Message 'Restore and configure demo database'
68 | $null = Restore-DbaDatabase -SqlInstance $SqlInstances[0] -Path "$SampleDatabasesPath\AdventureWorks2017.bak" -DatabaseName $DatabaseName
69 | $null = Set-DbaDbOwner -SqlInstance $SqlInstances[0] -Database $DatabaseName -TargetLogin sa
70 | $null = Set-DbaDbRecoveryModel -SqlInstance $SqlInstances[0] -Database $DatabaseName -RecoveryModel Full
71 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database $DatabaseName
72 |
73 | Write-PSFMessage -Level Host -Message 'finished'
74 |
75 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
76 |
--------------------------------------------------------------------------------
/AlwaysOn/01_setup_instances_SQL2016.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
6 | [string[]]$SqlInstances = @('SQL01\SQL2016', 'SQL02\SQL2016'),
7 | [string]$SQLServerServiceAccount = 'gMSA-SQLServer',
8 | [SecureString]$AdminPassword = (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force),
9 | [string]$SQLServerSourcesPath = '\\fs\Software\SQLServer\ISO',
10 | [string]$SQLServerPatchesPath = '\\fs\Software\SQLServer\CU',
11 | [string]$SampleDatabasesPath = '\\fs\SampleDatabases',
12 | [string]$DatabaseName = 'AdventureWorks'
13 | )
14 |
15 | $ErrorActionPreference = 'Stop'
16 |
17 | Import-Module -Name PSFramework
18 | Import-Module -Name dbatools
19 |
20 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
21 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
22 |
23 | try {
24 |
25 | $administratorCredential = [PSCredential]::new("$DomainName\Admin", $AdminPassword)
26 | $sqlServerCredential = [PSCredential]::new("$DomainName\$SQLServerServiceAccount$", [SecureString]::new())
27 |
28 | if ($null -eq (Get-ADServiceAccount -Filter "Name -eq '$SQLServerServiceAccount'") ) {
29 | throw "Service Account '$SQLServerServiceAccount' for SQL Server not found. Creation of this account is not part of this script but done in https://github.com/andreasjordan/demos/blob/master/AutomatedLab/CustomScripts/SQLServer_AlwaysOn.ps1"
30 | }
31 |
32 | Write-PSFMessage -Level Host -Message 'Change powerplan of cluster nodes to high performance'
33 | Set-DbaPowerPlan -ComputerName $ClusterNodes | Format-Table
34 |
35 | Write-PSFMessage -Level Host -Message 'Install SQL Server instances on cluster nodes'
36 | $installParams = @{
37 | SqlInstance = $SqlInstances
38 | Version = 2016
39 | Feature = 'Engine'
40 | Path = $SQLServerSourcesPath
41 | UpdateSourcePath = $SQLServerPatchesPath
42 | EngineCredential = $sqlServerCredential
43 | AgentCredential = $sqlServerCredential
44 | AuthenticationMode = 'Mixed'
45 | Credential = $administratorCredential
46 | Restart = $true
47 | EnableException = $false
48 | }
49 | $installResult = Install-DbaInstance @installParams
50 | $installResult | Format-Table
51 | if ($false -in $installResult.Successful) {
52 | throw "Install-DbaInstance not successful"
53 | }
54 |
55 | Write-PSFMessage -Level Host -Message 'Grant instant file initialization rights to SQL Server service account on cluster nodes'
56 | Set-DbaPrivilege -ComputerName $ClusterNodes -Type IFI
57 |
58 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instances: MaxMemory / MaxDop / CostThresholdForParallelism'
59 | Set-DbaMaxMemory -SqlInstance $SqlInstances -Max 2048 | Format-Table
60 | Set-DbaMaxDop -SqlInstance $SqlInstances | Format-Table
61 | Set-DbaSpConfigure -SqlInstance $SqlInstances -Name CostThresholdForParallelism -Value 50 | Format-Table
62 |
63 | Write-PSFMessage -Level Host -Message 'Restore and configure demo database'
64 | $null = Restore-DbaDatabase -SqlInstance $SqlInstances[0] -Path "$SampleDatabasesPath\AdventureWorks2016.bak" -DatabaseName $DatabaseName
65 | $null = Set-DbaDbOwner -SqlInstance $SqlInstances[0] -Database $DatabaseName -TargetLogin sa
66 | $null = Set-DbaDbRecoveryModel -SqlInstance $SqlInstances[0] -Database $DatabaseName -RecoveryModel Full
67 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database $DatabaseName
68 |
69 | Write-PSFMessage -Level Host -Message 'finished'
70 |
71 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
72 |
--------------------------------------------------------------------------------
/AlwaysOn/01_setup_instances_SQL2017.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
6 | [string[]]$SqlInstances = @('SQL01\SQL2017', 'SQL02\SQL2017'),
7 | [string]$SQLServerServiceAccount = 'gMSA-SQLServer',
8 | [SecureString]$AdminPassword = (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force),
9 | [string]$SQLServerSourcesPath = '\\fs\Software\SQLServer\ISO',
10 | [string]$SQLServerPatchesPath = '\\fs\Software\SQLServer\CU',
11 | [string]$SampleDatabasesPath = '\\fs\SampleDatabases',
12 | [string]$DatabaseName = 'AdventureWorks'
13 | )
14 |
15 | $ErrorActionPreference = 'Stop'
16 |
17 | Import-Module -Name PSFramework
18 | Import-Module -Name dbatools
19 |
20 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
21 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
22 |
23 | try {
24 |
25 | $administratorCredential = [PSCredential]::new("$DomainName\Admin", $AdminPassword)
26 | $sqlServerCredential = [PSCredential]::new("$DomainName\$SQLServerServiceAccount$", [SecureString]::new())
27 |
28 | if ($null -eq (Get-ADServiceAccount -Filter "Name -eq '$SQLServerServiceAccount'") ) {
29 | throw "Service Account '$SQLServerServiceAccount' for SQL Server not found. Creation of this account is not part of this script but done in https://github.com/andreasjordan/demos/blob/master/AutomatedLab/CustomScripts/SQLServer_AlwaysOn.ps1"
30 | }
31 |
32 | Write-PSFMessage -Level Host -Message 'Change powerplan of cluster nodes to high performance'
33 | Set-DbaPowerPlan -ComputerName $ClusterNodes | Format-Table
34 |
35 | Write-PSFMessage -Level Host -Message 'Install SQL Server instances on cluster nodes'
36 | $installParams = @{
37 | SqlInstance = $SqlInstances
38 | Version = 2017
39 | Feature = 'Engine'
40 | Path = $SQLServerSourcesPath
41 | UpdateSourcePath = $SQLServerPatchesPath
42 | EngineCredential = $sqlServerCredential
43 | AgentCredential = $sqlServerCredential
44 | AuthenticationMode = 'Mixed'
45 | Credential = $administratorCredential
46 | Restart = $true
47 | EnableException = $false
48 | }
49 | $installResult = Install-DbaInstance @installParams
50 | $installResult | Format-Table
51 | if ($false -in $installResult.Successful) {
52 | throw "Install-DbaInstance not successful"
53 | }
54 |
55 | Write-PSFMessage -Level Host -Message 'Grant instant file initialization rights to SQL Server service account on cluster nodes'
56 | Set-DbaPrivilege -ComputerName $ClusterNodes -Type IFI
57 |
58 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instances: MaxMemory / MaxDop / CostThresholdForParallelism'
59 | Set-DbaMaxMemory -SqlInstance $SqlInstances -Max 2048 | Format-Table
60 | Set-DbaMaxDop -SqlInstance $SqlInstances | Format-Table
61 | Set-DbaSpConfigure -SqlInstance $SqlInstances -Name CostThresholdForParallelism -Value 50 | Format-Table
62 |
63 | Write-PSFMessage -Level Host -Message 'Restore and configure demo database'
64 | $null = Restore-DbaDatabase -SqlInstance $SqlInstances[0] -Path "$SampleDatabasesPath\AdventureWorks2017.bak" -DatabaseName $DatabaseName
65 | $null = Set-DbaDbOwner -SqlInstance $SqlInstances[0] -Database $DatabaseName -TargetLogin sa
66 | $null = Set-DbaDbRecoveryModel -SqlInstance $SqlInstances[0] -Database $DatabaseName -RecoveryModel Full
67 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database $DatabaseName
68 |
69 | Write-PSFMessage -Level Host -Message 'finished'
70 |
71 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
72 |
--------------------------------------------------------------------------------
/AlwaysOn/02_setup_availability_group.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
4 | [string[]]$SqlInstances = @('SQL01', 'SQL02'),
5 | [string]$BackupPath = '\\fs\Backup',
6 | [string]$DatabaseName = 'AdventureWorks',
7 | [string]$AvailabilityGroupName = 'AdventureSQL',
8 | [System.Net.IPAddress]$AvailabilityGroupIP = '192.168.3.71',
9 | [string[]]$EndpointUrls = @('TCP://192.168.3.31:5023', 'TCP://192.168.3.32:5023')
10 | )
11 |
12 | $ErrorActionPreference = 'Stop'
13 |
14 | Import-Module -Name PSFramework
15 | Import-Module -Name dbatools
16 |
17 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
18 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
19 |
20 | try {
21 |
22 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instance service to enable Always On'
23 | Enable-DbaAgHadr -SqlInstance $SqlInstances -Force | Format-Table
24 |
25 | $availabilityGroupParameters = @{
26 | Primary = $SqlInstances[0]
27 | Secondary = $SqlInstances[1]
28 | Name = $AvailabilityGroupName
29 | IPAddress = $AvailabilityGroupIP
30 | Database = $DatabaseName
31 | ClusterType = 'Wsfc'
32 | ConfigureXESession = $true
33 | }
34 |
35 | # Special configuration for one of our clients:
36 | $availabilityGroupParameters['DtcSupport'] = $true
37 | $availabilityGroupParameters['ConnectionModeInSecondaryRole'] = 'AllowAllConnections'
38 | if ($EndpointUrls.Count -gt 0) {
39 | $availabilityGroupParameters['EndpointUrl'] = $EndpointUrls
40 | }
41 |
42 | Write-PSFMessage -Level Host -Message 'Add firewall rule for endpoint'
43 | $firewallConfig = @{
44 | DisplayName = 'SQL Server HADR endpoint'
45 | Name = 'SQL Server HADR endpoint'
46 | Group = 'SQL Server'
47 | Enabled = 'True'
48 | Direction = 'Inbound'
49 | Protocol = 'TCP'
50 | LocalPort = '5022'
51 | }
52 | if ($EndpointUrls.Count -gt 0) {
53 | # All ports should be the same, so we only take the frist one.
54 | $firewallConfig.LocalPort = $EndpointUrls[0] -replace '^.*:(\d+)$', '$1'
55 | }
56 | Invoke-Command -ComputerName $ClusterNodes -ArgumentList $firewallConfig -ScriptBlock {
57 | Param ($FirewallRuleParameters)
58 | $null = New-NetFirewallRule @FirewallRuleParameters
59 | }
60 |
61 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with automatic seeding'
62 | $availabilityGroupParameters['SeedingMode'] = 'Automatic'
63 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
64 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
65 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
66 |
67 |
68 | # This is optional just to show some more features:
69 |
70 | Write-PSFMessage -Level Host -Message 'waiting...'
71 | Start-Sleep -Seconds 30
72 |
73 | Write-PSFMessage -Level Host -Message 'Drop Always On Availability Group'
74 | $null = Remove-DbaAvailabilityGroup -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName
75 | $null = Remove-DbaDatabase -SqlInstance $SqlInstances[1] -Database $DatabaseName
76 | $null = Get-DbaEndpoint -SqlInstance $SqlInstances -Type DatabaseMirroring | Remove-DbaEndpoint -Confirm:$false
77 |
78 | Write-PSFMessage -Level Host -Message 'waiting...'
79 | Start-Sleep -Seconds 30
80 |
81 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with manual seeding'
82 | $availabilityGroupParameters['SeedingMode'] = 'Manual'
83 | $availabilityGroupParameters['SharedPath'] = $BackupPath
84 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
85 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
86 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
87 |
88 |
89 | Write-PSFMessage -Level Host -Message 'Add database to Always On Availability Group with backup-restore and automatic seeding'
90 | $null = New-DbaDatabase -SqlInstance $SqlInstances[0] -Name TestDB
91 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Full | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -NoRecovery
92 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Log | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -Continue -NoRecovery
93 | Add-DbaAgDatabase -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Database TestDB -Secondary $SqlInstances[1] -SeedingMode Automatic | Format-Table
94 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database TestDB | Format-Table
95 |
96 |
97 | Write-PSFMessage -Level Host -Message 'Configuring read only routing to use secondary for read only connections'
98 | $null = Set-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Replica $SqlInstances[0] -ReadonlyRoutingConnectionUrl "TCP://$($SqlInstances[0]):1433"
99 | $null = Set-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Replica $SqlInstances[1] -ReadonlyRoutingConnectionUrl "TCP://$($SqlInstances[1]):1433"
100 | $null = Set-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Replica $SqlInstances[0] -ReadOnlyRoutingList $SqlInstances[1], $SqlInstances[0]
101 | $null = Set-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Replica $SqlInstances[1] -ReadOnlyRoutingList $SqlInstances[0], $SqlInstances[1]
102 |
103 | Write-PSFMessage -Level Host -Message 'Testing read only routing to use secondary for read only connections'
104 | Connect-DbaInstance -SqlInstance $AvailabilityGroupName -Database $DatabaseName -ApplicationIntent ReadWrite | Format-Table
105 | Connect-DbaInstance -SqlInstance $AvailabilityGroupName -Database $DatabaseName -ApplicationIntent ReadOnly | Format-Table
106 |
107 |
108 | Write-PSFMessage -Level Host -Message 'finished'
109 |
110 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
111 |
112 |
--------------------------------------------------------------------------------
/AlwaysOn/02_setup_availability_group_SQL2016.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string[]]$SqlInstances = @('SQL01\SQL2016', 'SQL02\SQL2016'),
4 | [string]$BackupPath = '\\fs\Backup',
5 | [string]$DatabaseName = 'AdventureWorks',
6 | [string]$AvailabilityGroupName = 'AdventureSQL2016',
7 | [System.Net.IPAddress]$AvailabilityGroupIP = '192.168.3.73',
8 | [string[]]$EndpointUrls = @('TCP://192.168.3.31:5025', 'TCP://192.168.3.32:5025')
9 | )
10 |
11 | $ErrorActionPreference = 'Stop'
12 |
13 | Import-Module -Name PSFramework
14 | Import-Module -Name dbatools
15 |
16 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
17 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
18 |
19 | try {
20 |
21 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instance service to enable Always On'
22 | Enable-DbaAgHadr -SqlInstance $SqlInstances -Force | Format-Table
23 |
24 | $availabilityGroupParameters = @{
25 | Primary = $SqlInstances[0]
26 | Secondary = $SqlInstances[1]
27 | Name = $AvailabilityGroupName
28 | IPAddress = $AvailabilityGroupIP
29 | Port = 24330 # This is only needed if you have also run the 02_setup_availability_group.ps1 - as the other availability group already uses port 1433
30 | Database = $DatabaseName
31 | ClusterType = 'Wsfc'
32 | ConfigureXESession = $true
33 | }
34 |
35 | # Special configuration for one of our clients:
36 | $availabilityGroupParameters['DtcSupport'] = $true
37 | $availabilityGroupParameters['ConnectionModeInSecondaryRole'] = 'AllowAllConnections'
38 | if ($EndpointUrls.Count -gt 0) {
39 | $availabilityGroupParameters['EndpointUrl'] = $EndpointUrls
40 | }
41 |
42 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with automatic seeding'
43 | $availabilityGroupParameters['SeedingMode'] = 'Automatic'
44 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
45 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
46 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
47 |
48 |
49 | # This is optional just to show some more features:
50 |
51 | Write-PSFMessage -Level Host -Message 'waiting...'
52 | Start-Sleep -Seconds 30
53 |
54 | Write-PSFMessage -Level Host -Message 'Drop Always On Availability Group'
55 | $null = Remove-DbaAvailabilityGroup -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName
56 | $null = Remove-DbaDatabase -SqlInstance $SqlInstances[1] -Database $DatabaseName
57 | $null = Get-DbaEndpoint -SqlInstance $SqlInstances -Type DatabaseMirroring | Remove-DbaEndpoint -Confirm:$false
58 |
59 | Write-PSFMessage -Level Host -Message 'waiting...'
60 | Start-Sleep -Seconds 30
61 |
62 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with manual seeding'
63 | $availabilityGroupParameters['SeedingMode'] = 'Manual'
64 | $availabilityGroupParameters['SharedPath'] = $BackupPath
65 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
66 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
67 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
68 |
69 |
70 | Write-PSFMessage -Level Host -Message 'Add database to Always On Availability Group with backup-restore and automatic seeding'
71 | $null = New-DbaDatabase -SqlInstance $SqlInstances[0] -Name TestDB
72 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Full | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -NoRecovery
73 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Log | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -Continue -NoRecovery
74 | Add-DbaAgDatabase -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Database TestDB -Secondary $SqlInstances[1] -SeedingMode Automatic | Format-Table
75 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database TestDB | Format-Table
76 |
77 |
78 | Write-PSFMessage -Level Host -Message 'finished'
79 |
80 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
81 |
--------------------------------------------------------------------------------
/AlwaysOn/02_setup_availability_group_SQL2017.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string[]]$SqlInstances = @('SQL01\SQL2017', 'SQL02\SQL2017'),
4 | [string]$BackupPath = '\\fs\Backup',
5 | [string]$DatabaseName = 'AdventureWorks',
6 | [string]$AvailabilityGroupName = 'AdventureSQL2017',
7 | [System.Net.IPAddress]$AvailabilityGroupIP = '192.168.3.72',
8 | [string[]]$EndpointUrls = @('TCP://192.168.3.31:5024', 'TCP://192.168.3.32:5024')
9 | )
10 |
11 | $ErrorActionPreference = 'Stop'
12 |
13 | Import-Module -Name PSFramework
14 | Import-Module -Name dbatools
15 |
16 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
17 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
18 |
19 | try {
20 |
21 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instance service to enable Always On'
22 | Enable-DbaAgHadr -SqlInstance $SqlInstances -Force | Format-Table
23 |
24 | $availabilityGroupParameters = @{
25 | Primary = $SqlInstances[0]
26 | Secondary = $SqlInstances[1]
27 | Name = $AvailabilityGroupName
28 | IPAddress = $AvailabilityGroupIP
29 | Port = 14330 # This is only needed if you have also run the 02_setup_availability_group.ps1 - as the other availability group already uses port 1433
30 | Database = $DatabaseName
31 | ClusterType = 'Wsfc'
32 | ConfigureXESession = $true
33 | }
34 |
35 | # Special configuration for one of our clients:
36 | $availabilityGroupParameters['DtcSupport'] = $true
37 | $availabilityGroupParameters['ConnectionModeInSecondaryRole'] = 'AllowAllConnections'
38 | if ($EndpointUrls.Count -gt 0) {
39 | $availabilityGroupParameters['EndpointUrl'] = $EndpointUrls
40 | }
41 |
42 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with automatic seeding'
43 | $availabilityGroupParameters['SeedingMode'] = 'Automatic'
44 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
45 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
46 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
47 |
48 |
49 | # This is optional just to show some more features:
50 |
51 | Write-PSFMessage -Level Host -Message 'waiting...'
52 | Start-Sleep -Seconds 30
53 |
54 | Write-PSFMessage -Level Host -Message 'Drop Always On Availability Group'
55 | $null = Remove-DbaAvailabilityGroup -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName
56 | $null = Remove-DbaDatabase -SqlInstance $SqlInstances[1] -Database $DatabaseName
57 | $null = Get-DbaEndpoint -SqlInstance $SqlInstances -Type DatabaseMirroring | Remove-DbaEndpoint -Confirm:$false
58 |
59 | Write-PSFMessage -Level Host -Message 'waiting...'
60 | Start-Sleep -Seconds 30
61 |
62 | Write-PSFMessage -Level Host -Message 'Create Always On Availability Group with manual seeding'
63 | $availabilityGroupParameters['SeedingMode'] = 'Manual'
64 | $availabilityGroupParameters['SharedPath'] = $BackupPath
65 | New-DbaAvailabilityGroup @availabilityGroupParameters | Format-Table
66 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table
67 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table
68 |
69 |
70 | Write-PSFMessage -Level Host -Message 'Add database to Always On Availability Group with backup-restore and automatic seeding'
71 | $null = New-DbaDatabase -SqlInstance $SqlInstances[0] -Name TestDB
72 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Full | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -NoRecovery
73 | $null = Backup-DbaDatabase -SqlInstance $SqlInstances[0] -Database TestDB -Path $BackupPath -Type Log | Restore-DbaDatabase -SqlInstance $SqlInstances[1] -Continue -NoRecovery
74 | Add-DbaAgDatabase -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName -Database TestDB -Secondary $SqlInstances[1] -SeedingMode Automatic | Format-Table
75 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName -Database TestDB | Format-Table
76 |
77 |
78 | Write-PSFMessage -Level Host -Message 'finished'
79 |
80 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
81 |
82 |
--------------------------------------------------------------------------------
/AlwaysOn/03_add_SQL03_to_Cluster_and_AvailabilityGroup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string[]]$ClusterNodes = @('SQL01', 'SQL02'),
6 | [string[]]$SqlInstances = @('SQL01', 'SQL02'),
7 | [string]$NewClusterNode = 'SQL03',
8 | [string]$NewSqlInstance = 'SQL03',
9 | [string]$ClusterName = 'CLUSTER1',
10 | [string]$SQLServerServiceAccount = 'gMSA-SQLServer',
11 | [SecureString]$AdminPassword = (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force),
12 | [string]$SQLServerSourcesPath = '\\fs\Software\SQLServer\ISO',
13 | [string]$SQLServerPatchesPath = '\\fs\Software\SQLServer\CU',
14 | [string]$AvailabilityGroupName = 'AdventureSQL',
15 | [string]$BackupPath = '\\fs\Backup'
16 | )
17 |
18 | $ErrorActionPreference = 'Stop'
19 |
20 | Import-Module -Name PSFramework
21 | Import-Module -Name dbatools
22 |
23 | $PSDefaultParameterValues['*-Dba*:EnableException'] = $true
24 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
25 |
26 | try {
27 |
28 | Write-PSFMessage -Level Host -Message 'Install cluster feature on new node'
29 | $installResult = Invoke-Command -ComputerName $NewClusterNode -ScriptBlock { Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools }
30 | $installResult | Format-Table
31 | if ( $installResult.RestartNeeded -eq 'Yes' ) {
32 | # Restart is needed on Windows Server 2019
33 | Restart-Computer -ComputerName $NewClusterNode
34 | Start-Sleep -Seconds 60
35 | }
36 |
37 | Write-PSFMessage -Level Host -Message 'Add new node to cluster'
38 | Add-ClusterNode -Cluster $ClusterName -Name $NewClusterNode
39 | # This should output something, but it does not
40 |
41 | Write-PSFMessage -Level Host -Message 'Lets see if we have a new node'
42 | Get-ClusterNode -Cluster $ClusterName | Format-Table
43 | # Yes, we have
44 |
45 |
46 | # Begin of code from 01_setup_instances.ps1
47 |
48 | $administratorCredential = [PSCredential]::new("$DomainName\Admin", $AdminPassword)
49 | $sqlServerCredential = [PSCredential]::new("$DomainName\$SQLServerServiceAccount$", [SecureString]::new())
50 |
51 | Write-PSFMessage -Level Host -Message 'Change powerplan of new cluster node to high performance'
52 | Set-DbaPowerPlan -ComputerName $NewClusterNode | Format-Table
53 |
54 | Write-PSFMessage -Level Host -Message 'Install SQL Server instances on new cluster node'
55 | $installParams = @{
56 | SqlInstance = $NewSqlInstance
57 | Version = 2022
58 | Feature = 'Engine'
59 | Path = $SQLServerSourcesPath
60 | UpdateSourcePath = $SQLServerPatchesPath
61 | EngineCredential = $sqlServerCredential
62 | AgentCredential = $sqlServerCredential
63 | AuthenticationMode = 'Mixed'
64 | Credential = $administratorCredential
65 | Restart = $true
66 | EnableException = $false
67 | }
68 | $installResult = Install-DbaInstance @installParams
69 | $installResult | Format-Table
70 | if ($false -in $installResult.Successful) {
71 | throw "Install-DbaInstance not successful"
72 | }
73 |
74 | Write-PSFMessage -Level Host -Message 'Grant instant file initialization rights to SQL Server service account on new cluster node'
75 | Set-DbaPrivilege -ComputerName $NewClusterNode -Type IFI
76 |
77 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instances: MaxMemory / MaxDop / CostThresholdForParallelism'
78 | Set-DbaMaxMemory -SqlInstance $NewSqlInstance -Max 2048 | Format-Table
79 | Set-DbaMaxDop -SqlInstance $NewSqlInstance | Format-Table
80 | Set-DbaSpConfigure -SqlInstance $NewSqlInstance -Name CostThresholdForParallelism -Value 50 | Format-Table
81 |
82 | # End of code from 01_setup_instances.ps1
83 |
84 |
85 | # Begin of code from 02_setup_availability_group.ps1
86 |
87 | Write-PSFMessage -Level Host -Message 'Configure SQL Server instance service to enable Always On'
88 | Enable-DbaAgHadr -SqlInstance $NewSqlInstance -Force | Format-Table
89 |
90 | # End of code from 02_setup_availability_group.ps1
91 |
92 |
93 | $ag = Get-DbaAvailabilityGroup -SqlInstance $SqlInstances -AvailabilityGroup $AvailabilityGroupName | Where-Object LocalReplicaRole -eq Primary
94 | $primaryReplica = Get-DbaAgReplica -SqlInstance $ag.Parent -AvailabilityGroup $AvailabilityGroupName | Where-Object Role -eq Primary
95 |
96 | $replicaParameters = @{
97 | SqlInstance = $NewSqlInstance
98 | AvailabilityMode = $primaryReplica.AvailabilityMode
99 | FailoverMode = $primaryReplica.FailoverMode
100 | BackupPriority = $primaryReplica.BackupPriority
101 | ConnectionModeInPrimaryRole = $primaryReplica.ConnectionModeInPrimaryRole
102 | ConnectionModeInSecondaryRole = $primaryReplica.ConnectionModeInSecondaryRole
103 | SeedingMode = 'Manual'
104 | ConfigureXESession = $true
105 | }
106 |
107 | # Special configuration for one of our clients:
108 | $replicaParameters['EndpointUrl'] = 'TCP://192.168.3.33:5023'
109 |
110 | Write-PSFMessage -Level Host -Message 'Adding replica to Always On Availability Group with manual seeding'
111 | $ag | Add-DbaAgReplica @replicaParameters | Format-Table
112 |
113 | # Wait for new replica to connect to Availability Group
114 | Write-PSFMessage -Level Host -Message 'waiting...'
115 | Start-Sleep -Seconds 30
116 |
117 | Write-PSFMessage -Level Host -Message 'Adding databases to Always On Availability Group with manual seeding'
118 | Add-DbaAgDatabase -SqlInstance $primaryReplica.SqlInstance -AvailabilityGroup $AvailabilityGroupName -Database $ag.AvailabilityDatabases.Name -SharedPath $BackupPath | Format-Table
119 | Get-DbaAgReplica -SqlInstance $primaryReplica.SqlInstance -AvailabilityGroup $AvailabilityGroupName | Format-Table
120 | Get-DbaAgDatabase -SqlInstance $NewSqlInstance -AvailabilityGroup $AvailabilityGroupName | Format-Table
121 |
122 |
123 | # This is optional just to show some more features:
124 |
125 | Write-PSFMessage -Level Host -Message 'waiting...'
126 | Start-Sleep -Seconds 30
127 |
128 | $null = Remove-DbaAgReplica -SqlInstance $ag.Parent -AvailabilityGroup $AvailabilityGroupName -Replica $NewSqlInstance
129 | $null = Get-DbaDatabase -SqlInstance $NewSqlInstance -Database $ag.AvailabilityDatabases.Name | Remove-DbaDatabase
130 | $null = Get-DbaEndpoint -SqlInstance $NewSqlInstance -Type DatabaseMirroring | Remove-DbaEndpoint -Confirm:$false
131 |
132 | Write-PSFMessage -Level Host -Message 'Adding replica to Always On Availability Group with automatic seeding'
133 | $replicaParameters['SeedingMode'] = 'Automatic'
134 | $ag | Add-DbaAgReplica @replicaParameters | Format-Table
135 |
136 | # Wait for new replica to connect to Availability Group and for automatic seeding to move databases to new replica
137 | Write-PSFMessage -Level Host -Message 'waiting...'
138 | Start-Sleep -Seconds 30
139 |
140 | Get-DbaAgReplica -SqlInstance $primaryReplica.SqlInstance -AvailabilityGroup $AvailabilityGroupName | Format-Table
141 | Get-DbaAgDatabase -SqlInstance $NewSqlInstance -AvailabilityGroup $AvailabilityGroupName | Format-Table
142 |
143 |
144 | Write-PSFMessage -Level Host -Message 'finished'
145 |
146 | } catch { Write-PSFMessage -Level Warning -Message 'failed' -ErrorRecord $_ }
147 |
--------------------------------------------------------------------------------
/AlwaysOn/99_remove.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'ORDIX',
4 | [string]$DomainController = 'DC',
5 | [string]$ClusterName = 'CLUSTER1',
6 | [SecureString]$AdminPassword = (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force),
7 | [string]$SQLServerSourcesPath = '\\fs\Software\SQLServer\ISO',
8 | [string]$BackupPath = '\\fs\Backup'
9 | )
10 |
11 | $ErrorActionPreference = 'Stop'
12 |
13 | Import-Module -Name PSFramework
14 | Import-Module -Name dbatools
15 |
16 | $PSDefaultParameterValues['*-Dba*:Confirm'] = $false
17 |
18 | $administratorCredential = New-Object -TypeName PSCredential -ArgumentList "$DomainName\Admin", $AdminPassword
19 |
20 | Write-PSFMessage -Level Host -Message 'Get Cluster Nodes'
21 | if ($PSVersionTable.PSVersion.Major -eq 5) {
22 | $clusterNodes = Get-Cluster -Name $ClusterName | Get-ClusterNode
23 | } else {
24 | $clusterNodes = Get-ClusterNode -Cluster $ClusterName
25 | }
26 |
27 | Write-PSFMessage -Level Host -Message 'Get SQL Server instances on cluster nodes'
28 | $sqlInstances = Find-DbaInstance -ComputerName $clusterNodes.Name
29 |
30 | Write-PSFMessage -Level Host -Message 'Get Availability Groups'
31 | $ags = Get-DbaAvailabilityGroup -SqlInstance $sqlInstances.SqlInstance | Where-Object LocalReplicaRole -eq 'Primary'
32 |
33 | foreach ($ag in $ags) {
34 | Write-PSFMessage -Level Host -Message "Remove Availability Group $($ag.AvailabilityGroup) to remove cluster resource"
35 | $ag | Remove-DbaAvailabilityGroup -Confirm:$false
36 | Get-ADComputer -Filter "Name -eq '$($ag.AvailabilityGroup)'" | Remove-ADComputer -Confirm:$false
37 | }
38 |
39 | foreach ($inst in $sqlInstances) {
40 | Write-PSFMessage -Level Host -Message "Remove SQL Server instance $($inst.SqlInstance)"
41 |
42 | # What information do we need?
43 | # * Version for Install-DbaInstance to select the correct setup.exe
44 | # * InstancePath for Remove-Item to remove all left over files
45 | try {
46 | # Try to get information from running instance
47 | $server = Connect-DbaInstance -SqlInstance $inst.SqlInstance
48 | $instanceVersion = ($server.GetSqlServerVersionName() -split ' ')[-1]
49 | if (-not $instanceVersion) { $instanceVersion = 2022 }
50 | $instancePath = $server.RootDirectory -replace 'MSSQL$', ''
51 | } catch {
52 | # Fallback to information about the service
53 | $service = Get-DbaService -ComputerName $inst.ComputerName -InstanceName $inst.InstanceName -Type Engine -EnableException
54 | $instanceVersion = switch ($service.BinaryPath -replace '^.*MSSQL(\d\d).*$', '$1') { 16 { 2022 } 15 { 2019 } 14 { 2017 } 13 { 2016 } 12 { 2014 } 11 { 2012 } }
55 | $instancePath = $service.BinaryPath -replace '^"?(.*)MSSQL\\Binn\\sqlservr\.exe.*$', '$1'
56 | }
57 | $params = @{
58 | SqlInstance = $inst.SqlInstance
59 | Version = $instanceVersion
60 | Feature = 'Engine'
61 | Configuration = @{ ACTION = 'Uninstall' }
62 | Path = $SQLServerSourcesPath
63 | Restart = $true
64 | Credential = $administratorCredential
65 | Confirm = $false
66 | }
67 | $result = Install-DbaInstance @params
68 | if (-not $result.Successful) {
69 | $result.Log | Set-Clipboard
70 | throw "Uninstall failed, see clipboard for details"
71 | }
72 |
73 | # Remove firewall rule
74 | Get-DbaFirewallRule -SqlInstance $inst.SqlInstance | Remove-DbaFirewallRule
75 |
76 | # Remove directory
77 | Invoke-Command -ComputerName $inst.ComputerName -ScriptBlock {
78 | param($path)
79 | Remove-Item -Path $path -Recurse -Force
80 | } -ArgumentList $instancePath
81 | }
82 |
83 | foreach ($node in $clusterNodes) {
84 | Write-PSFMessage -Level Host -Message "Remove SQL Server base directory on $($node.Name)"
85 | Invoke-Command -ComputerName $node.Name -ScriptBlock { Remove-Item -Path 'C:\Program Files\Microsoft SQL Server' -Recurse -Force }
86 | }
87 |
88 | Write-PSFMessage -Level Host -Message "Remove backups from backup directory"
89 | Get-ChildItem -Path $BackupPath | Where-Object -Property Name -Match -Value '_\d{12}.(bak|trn)$' | Remove-Item
90 |
91 | Write-PSFMessage -Level Host -Message "Remove cluster"
92 | if ($PSVersionTable.PSVersion.Major -eq 5) {
93 | Get-Cluster -Name $ClusterName | Remove-Cluster -Force
94 | } else {
95 | Remove-Cluster -Cluster $ClusterName -Force
96 | }
97 | Get-ADComputer -Filter "Name -eq '$ClusterName'" | Remove-ADComputer -Confirm:$false
98 | Invoke-Command -ComputerName $DomainController -ScriptBlock { Remove-SmbShare -Name "WindowsClusterQuorum_$using:ClusterName" -Force }
99 | Invoke-Command -ComputerName $DomainController -ScriptBlock { Remove-Item -Path "C:\WindowsClusterQuorum_$using:ClusterName" -Recurse }
100 | Invoke-Command -ComputerName $clusterNodes.Name -ScriptBlock { Remove-WindowsFeature -Name Failover-Clustering -IncludeManagementTools } | Format-Table
101 |
102 | Write-PSFMessage -Level Host -Message 'Restart server'
103 | Restart-Computer -ComputerName $clusterNodes.Name -Force
104 |
105 |
106 | # Maybe needed:
107 | # Enable-WSManCredSSP -Role Client -DelegateComputer $ClusterNodes -Force | Out-Null
108 | # Invoke-Command -ComputerName $ClusterNodes -ScriptBlock { Enable-WSManCredSSP -Role Server -Force } | Out-Null
109 |
110 | # Should be reset as well:
111 | # Set-DbaPrivilege -ComputerName $ClusterNodes -Type IFI
112 |
--------------------------------------------------------------------------------
/AlwaysOn/README.md:
--------------------------------------------------------------------------------
1 | # Automated test for Always On Availability Groups
2 |
3 | I use these scripts to have a repeatable test for the dbatools commands related to Always On Availability Groups.
4 |
5 | I use [this](../AutomatedLab/CustomScripts/SQLServer_AlwaysOn.ps1) lab to run the scripts.
6 |
7 | I have tested the scripts with the current 2.1.5 version of dbatools.
8 |
--------------------------------------------------------------------------------
/AlwaysOn_DatabaseOwner/01_database_owner.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Stop'
2 |
3 | Import-Module -Name dbatools
4 |
5 | $SqlInstances = 'SRV1', 'SRV2'
6 | $BackupPath = '\\WIN10\SQLServerBackups'
7 | $DatabaseName = 'Test01'
8 | $AvailabilityGroupName = 'AdventureSQL'
9 |
10 |
11 | # We need three logins with sysadmin role, one for creating the database, one for restoring the database and one for as the new owner
12 |
13 | $credAdmin1 = Get-Credential -Message 'SQL Admin 1' -UserName admin1
14 | $credAdmin2 = Get-Credential -Message 'SQL Admin 2' -UserName admin2
15 | $credAdmin3 = Get-Credential -Message 'SQL Admin 2' -UserName admin3
16 |
17 |
18 | # Take care that we have mixed authentication mode
19 |
20 | $server = Connect-DbaInstance -SqlInstance $SqlInstances
21 | foreach ($srv in $server) {
22 | if ($srv.LoginMode -ne 'Mixed') {
23 | $srv.LoginMode = 'Mixed'
24 | $srv.Alter()
25 | $null = Restart-DbaService -ComputerName $srv.ComputerName -InstanceName $srv.DbaInstanceName -Type Engine -Force
26 | }
27 | }
28 |
29 |
30 | # Take care the the first instance is the primary of the availability group
31 |
32 | if ((Get-DbaAvailabilityGroup -SqlInstance $server[0] -AvailabilityGroup $AvailabilityGroupName).LocalReplicaRole -ne 'Primary') {
33 | $null = Invoke-DbaAgFailover -SqlInstance $server[0] -AvailabilityGroup $AvailabilityGroupName -Confirm:$false
34 | }
35 |
36 |
37 | # Create logins, add them to sysadmin and create a connection with that login
38 |
39 | $loginAdmin1 = New-DbaLogin -SqlInstance $server[0] -Login $credAdmin1.UserName -SecurePassword $credAdmin1.Password
40 | $null = New-DbaLogin -SqlInstance $server[1] -Login $credAdmin1.UserName -SecurePassword $credAdmin1.Password -Sid $loginAdmin1.Sid
41 | $null = Set-DbaLogin -SqlInstance $server -Login $credAdmin1.UserName -AddRole sysadmin
42 | $serverAdmin1 = Connect-DbaInstance -SqlInstance $SqlInstances -SqlCredential $credAdmin1
43 |
44 | $loginAdmin2 = New-DbaLogin -SqlInstance $server[0] -Login $credAdmin2.UserName -SecurePassword $credAdmin2.Password
45 | $null = New-DbaLogin -SqlInstance $server[1] -Login $credAdmin2.UserName -SecurePassword $credAdmin2.Password -Sid $loginAdmin2.Sid
46 | $null = Set-DbaLogin -SqlInstance $server -Login $credAdmin2.UserName -AddRole sysadmin
47 | $serverAdmin2 = Connect-DbaInstance -SqlInstance $SqlInstances -SqlCredential $credAdmin2
48 |
49 | $loginAdmin3 = New-DbaLogin -SqlInstance $server[0] -Login $credAdmin3.UserName -SecurePassword $credAdmin3.Password
50 | $null = New-DbaLogin -SqlInstance $server[1] -Login $credAdmin3.UserName -SecurePassword $credAdmin3.Password -Sid $loginAdmin3.Sid
51 | $null = Set-DbaLogin -SqlInstance $server -Login $credAdmin3.UserName -AddRole sysadmin
52 | $serverAdmin3 = Connect-DbaInstance -SqlInstance $SqlInstances -SqlCredential $credAdmin3
53 |
54 |
55 | # Create the database as admin1 and create the first backups
56 |
57 | $null = New-DbaDatabase -SqlInstance $serverAdmin1[0] -Name $DatabaseName
58 | $fullBackup = Backup-DbaDatabase -SqlInstance $serverAdmin1[0] -Database $DatabaseName -Path $BackupPath -Type Full
59 | $logBackup = Backup-DbaDatabase -SqlInstance $serverAdmin1[0] -Database $DatabaseName -Path $BackupPath -Type Log
60 |
61 |
62 | # Restore the database as admin2 and add the database to the availability group
63 |
64 | $null = $fullBackup | Restore-DbaDatabase -SqlInstance $serverAdmin2[1] -NoRecovery
65 | $null = $logBackup | Restore-DbaDatabase -SqlInstance $serverAdmin2[1] -NoRecovery -Continue
66 | $null = Add-DbaAgDatabase -SqlInstance $serverAdmin2[0] -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName -Secondary $serverAdmin2[1] -SeedingMode Automatic
67 |
68 |
69 | # Get information about the database
70 |
71 | Get-DbaAgDatabase -SqlInstance $server -AvailabilityGroup $AvailabilityGroupName -Database $DatabaseName | Format-Table -Property SqlInstance, Name, SynchronizationState
72 | Get-DbaDatabase -SqlInstance $server -Database $DatabaseName | Format-Table -Property SqlInstance, Name, Owner
73 |
74 | <#
75 |
76 | SqlInstance Name SynchronizationState
77 | ----------- ---- --------------------
78 | SRV1 Test01 Synchronized
79 | SRV2 Test01 Synchronized
80 |
81 |
82 | SqlInstance Name Owner
83 | ----------- ---- -----
84 | SRV1 Test01 admin1
85 | SRV2 Test01 admin2
86 |
87 | #>
88 |
89 | # Result: Owner is different
90 |
91 |
92 | # Change the owner of the database which is only possible at the primary instance
93 |
94 | $null = Set-DbaDbOwner -SqlInstance $server[0] -Database $DatabaseName -TargetLogin $credAdmin3.UserName
95 | Get-DbaDatabase -SqlInstance $server -Database $DatabaseName | Format-Table -Property SqlInstance, Name, Owner # Ups, not updated - but maybe its only the SMO...
96 | Get-DbaDatabase -SqlInstance $SqlInstances -Database $DatabaseName | Format-Table -Property SqlInstance, Name, Owner # Yes, that's a bug...
97 | # Let's refresh the SMO (like hitting F5 in SSMS):
98 | $server[0].Databases[$DatabaseName].Refresh()
99 | Get-DbaDatabase -SqlInstance $server -Database $DatabaseName | Format-Table -Property SqlInstance, Name, Owner # Ok, now the correct info is there
100 |
101 | <#
102 |
103 | SqlInstance Name Owner
104 | ----------- ---- -----
105 | SRV1 Test01 admin3
106 | SRV2 Test01 admin2
107 |
108 | #>
109 |
110 | # Result: The owner is not changed at the secondary instance. So this info is not part of the database but part of the instance
111 |
112 | # Here is where the info is stored
113 |
114 | $server.Query("SELECT @@SERVERNAME AS server_name, db.name AS database_name, sp.name AS owner_name FROM sys.databases db JOIN sys.server_principals sp ON db.owner_sid = sp.sid WHERE db.name = '$DatabaseName'")
115 |
116 | <#
117 |
118 | server_name database_name owner_name
119 | ----------- ------------- ----------
120 | SRV1 Test01 admin3
121 | SRV2 Test01 admin2
122 |
123 | #>
124 |
125 |
126 | # How to change the owner on the secondary? Fail over, change owner, fail back
127 |
128 | $null = Invoke-DbaAgFailover -SqlInstance $server[1] -AvailabilityGroup $AvailabilityGroupName -Confirm:$false
129 | $null = Set-DbaDbOwner -SqlInstance $server[1] -Database $DatabaseName -TargetLogin $credAdmin3.UserName # Ups, SMO is not up to date again...
130 | $null = Set-DbaDbOwner -SqlInstance $SqlInstances[1] -Database $DatabaseName -TargetLogin $credAdmin3.UserName # That works
131 | $null = Invoke-DbaAgFailover -SqlInstance $server[0] -AvailabilityGroup $AvailabilityGroupName -Confirm:$false
132 |
133 |
134 | # Let's see the result (and use new SMOs to get up to date data)
135 |
136 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $AvailabilityGroupName | Format-Table -Property Name, AvailabilityGroup, Role
137 | Get-DbaDatabase -SqlInstance $SqlInstances -Database $DatabaseName | Format-Table -Property SqlInstance, Name, Owner
138 |
139 | <#
140 |
141 | Name AvailabilityGroup Role
142 | ---- ----------------- ----
143 | SRV1 AdventureSQL Primary
144 | SRV2 AdventureSQL Secondary
145 |
146 |
147 | SqlInstance Name Owner
148 | ----------- ---- -----
149 | SRV1 Test01 admin3
150 | SRV2 Test01 admin3
151 |
152 | #>
153 |
154 |
155 | # Cleanup
156 |
157 | $null = Remove-DbaAgDatabase -SqlInstance $SqlInstances[0] -Database $DatabaseName -AvailabilityGroup $AvailabilityGroupName -Confirm:$false
158 | $null = Remove-DbaDatabase -SqlInstance $SqlInstances -Database $DatabaseName -Confirm:$false
159 | $null = Remove-DbaLogin -SqlInstance $SqlInstances -Login $credAdmin1.UserName, $credAdmin2.UserName, $credAdmin3.UserName -Force
160 | Remove-DbaDbBackupRestoreHistory -SqlInstance $SqlInstances -Database $DatabaseName -Confirm:$false
161 | Get-ChildItem -Path $BackupPath -Filter ($DatabaseName + '*') | Remove-Item
162 |
--------------------------------------------------------------------------------
/AlwaysOn_MultiInstance/00_setup_cluster.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'COMPANY',
4 | [string]$DomainController = 'DOM1',
5 | [string[]]$ClusterNodes = @('SRV1', 'SRV2'),
6 | [string]$ClusterName = 'SQLCluster',
7 | [string]$ClusterIP = '192.168.3.70'
8 | )
9 |
10 | function Write-LocalWarning {
11 | param (
12 | [string]$Message
13 | )
14 | Write-Warning -Message ('{0}: {1}' -f (Get-Date), $Message)
15 | }
16 |
17 | function Write-LocalHost {
18 | param (
19 | [string]$Message,
20 | [string]$ForegroundColor = 'Yellow'
21 | )
22 | Microsoft.PowerShell.Utility\Write-Host -Object ('{0}: {1}' -f (Get-Date), $Message) -ForegroundColor $ForegroundColor
23 | }
24 |
25 | function Write-LocalVerbose {
26 | param (
27 | [string]$Message
28 | )
29 | Write-Verbose -Message ('{0}: {1}' -f (Get-Date), $Message)
30 | }
31 |
32 | $ErrorActionPreference = 'Stop'
33 |
34 | Write-LocalHost -Message 'Install cluster freature on each node'
35 | Invoke-Command -ComputerName $ClusterNodes -ScriptBlock { Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools } | Format-Table
36 |
37 | Write-LocalHost -Message 'Run cluster test and display result in web browser'
38 | $clusterTest = Test-Cluster -Node $ClusterNodes
39 | &$clusterTest.FullName
40 |
41 | Write-LocalHost -Message 'Create the cluster'
42 | $cluster = New-Cluster -Name $ClusterName -Node $ClusterNodes -StaticAddress $ClusterIP
43 |
44 | Write-LocalHost -Message 'Create a share as cluster quorum and configure the cluster'
45 | Invoke-Command -ComputerName $DomainController -ScriptBlock {
46 | $null = New-Item -Path "C:\WindowsClusterQuorum_$using:ClusterName" -ItemType Directory
47 | $null = New-SmbShare -Path "C:\WindowsClusterQuorum_$using:ClusterName" -Name "WindowsClusterQuorum_$using:ClusterName"
48 | $null = Grant-SmbShareAccess -Name "WindowsClusterQuorum_$using:ClusterName" -AccountName "$using:DomainName\$using:ClusterName$" -AccessRight Full -Force
49 | }
50 | $cluster | Set-ClusterQuorum -NodeAndFileShareMajority "\\$DomainController\WindowsClusterQuorum_$ClusterName" | Format-List
51 |
52 | Write-LocalHost -Message 'finished'
53 |
--------------------------------------------------------------------------------
/AlwaysOn_MultiInstance/01_setup_instances.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string[]]$ClusterNodes = @('SRV1', 'SRV2'),
4 | [string[]]$InstanceNames = @('MSSQLSERVER', 'SQL2017', 'SQL2016', 'SQL2014'),
5 | [string[]]$SqlVersions = @('2019', '2017', '2016', '2014'),
6 | [PSCredential]$AdministratorCredential = (New-Object -TypeName PSCredential -ArgumentList "COMPANY\Administrator", (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force)),
7 | [PSCredential]$SqlServerCredential = (New-Object -TypeName PSCredential -ArgumentList "COMPANY\SQLServer", (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force)),
8 | [string]$SQLServerSourcesPath = '\\WIN10\SQLServerSources',
9 | [string]$SQLServerPatchesPath = '\\WIN10\SQLServerPatches',
10 | [string]$BackupPath = '\\WIN10\SQLServerBackups',
11 | [string]$DatabaseName = 'AdventureWorks'
12 | )
13 |
14 | function Write-LocalWarning {
15 | param (
16 | [string]$Message
17 | )
18 | Write-Warning -Message ('{0}: {1}' -f (Get-Date), $Message)
19 | }
20 |
21 | function Write-LocalHost {
22 | param (
23 | [string]$Message,
24 | [string]$ForegroundColor = 'Yellow'
25 | )
26 | Microsoft.PowerShell.Utility\Write-Host -Object ('{0}: {1}' -f (Get-Date), $Message) -ForegroundColor $ForegroundColor
27 | }
28 |
29 | function Write-LocalVerbose {
30 | param (
31 | [string]$Message
32 | )
33 | Write-Verbose -Message ('{0}: {1}' -f (Get-Date), $Message)
34 | }
35 |
36 | $ErrorActionPreference = 'Stop'
37 |
38 | $instances = @()
39 | foreach ( $nr in 0 .. ($InstanceNames.Count - 1) ) {
40 | $instances += [PSCustomObject]@{
41 | InstanceName = $InstanceNames[$nr]
42 | SqlVersion = $SqlVersions[$nr]
43 | }
44 | }
45 |
46 | $SQLServerServiceAccountName = ($SqlServerCredential.UserName -split '\\')[1]
47 | if ( $null -eq (Get-ADUser -Filter "Name -eq '$SQLServerServiceAccountName'") ) {
48 | Write-LocalHost -Message 'Creating user for SQL Server service account and grant access to backup share'
49 | New-ADUser -Name $SQLServerServiceAccountName -AccountPassword $sqlServerCredential.Password -PasswordNeverExpires:$true -Enabled:$true
50 | $null = Grant-SmbShareAccess -Name SQLServerBackups -AccountName $SqlServerCredential.UserName -AccessRight Full -Force
51 | }
52 |
53 | Write-LocalHost -Message 'Import module dbatools'
54 | Import-Module -Name dbatools -MinimumVersion 1.0.124
55 |
56 | Write-LocalHost -Message 'Change powerplan of cluster nodes to high performance'
57 | Set-DbaPowerPlan -ComputerName $ClusterNodes | Format-Table
58 |
59 | foreach ( $instance in $instances ) {
60 | $sqlInstances = @();
61 | foreach ( $node in $ClusterNodes ) {
62 | $sqlInstances += "$node\$($instance.InstanceName)"
63 | }
64 |
65 | Write-LocalHost -Message "Install SQL Server $($instance.SqlVersion) instances on cluster nodes"
66 | $installResult = Install-DbaInstance -SqlInstance $sqlInstances -Version $instance.SqlVersion -Feature Engine `
67 | -EngineCredential $SqlServerCredential -AgentCredential $SqlServerCredential `
68 | -Path $SQLServerSourcesPath -UpdateSourcePath $SQLServerPatchesPath -Authentication Credssp -Credential $AdministratorCredential -Confirm:$false
69 | $installResult | Format-Table
70 | if ( $installResult.Notes -match 'restart' ) {
71 | Write-LocalHost -Message 'Restarting cluster nodes'
72 | Restart-Computer -ComputerName $ClusterNodes
73 | Start-Sleep -Seconds 120
74 | }
75 |
76 | Write-LocalHost -Message 'Configure SQL Server instances: MaxMemory / MaxDop / CostThresholdForParallelism'
77 | Set-DbaMaxMemory -SqlInstance $sqlInstances -Max 2048 | Format-Table
78 | Set-DbaMaxDop -SqlInstance $sqlInstances | Format-Table
79 | Set-DbaSpConfigure -SqlInstance $sqlInstances -Name CostThresholdForParallelism -Value 50 | Format-Table
80 |
81 | Write-LocalHost -Message 'Restore and configure demo database'
82 | $dbUpgradeNeeded = $false
83 | $dbVersion = $instance.SqlVersion
84 | if ( $dbVersion -eq '2019' ) {
85 | $dbVersion = '2017'
86 | $dbUpgradeNeeded = $true
87 | }
88 | Restore-DbaDatabase -SqlInstance $sqlInstances[0] -Path "$BackupPath\AdventureWorks$dbVersion.bak" -DatabaseName $DatabaseName | Out-Null
89 | $Database = Get-DbaDatabase -SqlInstance $sqlInstances[0] -Database $DatabaseName
90 | $Database.RecoveryModel = 'Full'
91 | $Database.Alter()
92 | if ( $dbUpgradeNeeded ) {
93 | Write-LocalHost -Message "Upgrade demo database"
94 | Invoke-DbaDbUpgrade -SqlInstance $sqlInstances[0] -Database $DatabaseName | Format-Table
95 | }
96 | $null = Backup-DbaDatabase -SqlInstance $sqlInstances[0] -Database $DatabaseName
97 | }
98 |
99 | Write-LocalHost -Message 'Grant instant file initialization rights to SQL Server service account on cluster nodes'
100 | Set-DbaPrivilege -ComputerName $ClusterNodes -Type IFI
101 |
102 | Write-LocalHost -Message 'finished'
103 |
--------------------------------------------------------------------------------
/AlwaysOn_MultiInstance/02_setup_availability_group.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string[]]$ClusterNodes = @('SRV1', 'SRV2'),
4 | [string[]]$InstanceNames = @('MSSQLSERVER', 'SQL2017', 'SQL2016', 'SQL2014'),
5 | [string[]]$SqlVersions = @('2019', '2017', '2016', '2014'),
6 | [int[]]$HadrEndpointPorts = @(5022, 5023, 5024, 5025),
7 | [string[]]$AvailabilityGroupNames = @('Adventure2019', 'Adventure2017', 'Adventure2016', 'Adventure2014'),
8 | [System.Net.IPAddress[]]$AvailabilityGroupIPs = @('192.168.3.71', '192.168.3.72', '192.168.3.73', '192.168.3.74'),
9 | [PSCredential]$AdministratorCredential = (New-Object -TypeName PSCredential -ArgumentList "COMPANY\Administrator", (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force)),
10 | [PSCredential]$SqlServerCredential = (New-Object -TypeName PSCredential -ArgumentList "COMPANY\SQLServer", (ConvertTo-SecureString -String 'P@ssw0rd' -AsPlainText -Force)),
11 | [string]$BackupPath = '\\WIN10\SQLServerBackups',
12 | [string]$DatabaseName = 'AdventureWorks'
13 | )
14 |
15 | function Write-LocalWarning {
16 | param (
17 | [string]$Message
18 | )
19 | Write-Warning -Message ('{0}: {1}' -f (Get-Date), $Message)
20 | }
21 |
22 | function Write-LocalHost {
23 | param (
24 | [string]$Message,
25 | [string]$ForegroundColor = 'Yellow'
26 | )
27 | Microsoft.PowerShell.Utility\Write-Host -Object ('{0}: {1}' -f (Get-Date), $Message) -ForegroundColor $ForegroundColor
28 | }
29 |
30 | function Write-LocalVerbose {
31 | param (
32 | [string]$Message
33 | )
34 | Write-Verbose -Message ('{0}: {1}' -f (Get-Date), $Message)
35 | }
36 |
37 | $ErrorActionPreference = 'Stop'
38 |
39 | $instances = @()
40 | foreach ( $nr in 0 .. ($InstanceNames.Count - 1) ) {
41 | $instances += [PSCustomObject]@{
42 | InstanceName = $InstanceNames[$nr]
43 | SqlVersion = $SqlVersions[$nr]
44 | HadrEndpointPort = $HadrEndpointPorts[$nr]
45 | AvailabilityGroupName = $AvailabilityGroupNames[$nr]
46 | AvailabilityGroupIP = $AvailabilityGroupIPs[$nr]
47 | }
48 | }
49 |
50 | Write-LocalHost -Message 'Import module dbatools'
51 | Import-Module -Name dbatools -MinimumVersion 1.0.124
52 |
53 | foreach ( $instance in $instances ) {
54 | $sqlInstances = @();
55 | foreach ( $node in $ClusterNodes ) {
56 | $sqlInstances += "$node\$($instance.InstanceName)"
57 | }
58 |
59 | Write-LocalHost -Message "Configure SQL Server $($instance.SqlVersion) instance service to enable Always On"
60 | Enable-DbaAgHadr -SqlInstance $sqlInstances -Force | Format-Table
61 |
62 | Write-LocalHost -Message 'Configure and start extended event session AlwaysOn_health'
63 | Get-DbaXESession -SqlInstance $sqlInstances -Session AlwaysOn_health | ForEach-Object -Process { $_.AutoStart = $true ; $_.Alter() ; $_ | Start-DbaXESession } | Format-Table
64 |
65 | Write-LocalHost -Message 'Create endpoints'
66 | $endpoint = New-DbaEndpoint -SqlInstance $sqlInstances -Name hadr_endpoint -Type DatabaseMirroring -EndpointEncryption Supported -EncryptionAlgorithm Aes -Port $instance.HadrEndpointPort
67 | $endpoint | Start-DbaEndpoint | Format-Table
68 |
69 | $availabilityGroupParameters = @{
70 | Primary = $SqlInstances[0]
71 | Secondary = $SqlInstances[1]
72 | Name = $instance.AvailabilityGroupName
73 | IPAddress = $instance.AvailabilityGroupIP
74 | Database = $DatabaseName
75 | ClusterType = 'Wsfc'
76 | Confirm = $false
77 | }
78 | Write-LocalHost -Message 'Create Always On Availability Group with manual seeding'
79 | New-DbaAvailabilityGroup @availabilityGroupParameters -SeedingMode Manual -SharedPath $BackupPath | Format-Table
80 | #New-DbaAvailabilityGroup @availabilityGroupParameters -SeedingMode Automatic | Format-Table
81 | Get-DbaAgReplica -SqlInstance $SqlInstances[0] -AvailabilityGroup $instance.AvailabilityGroupName | Format-Table
82 | Get-DbaAgDatabase -SqlInstance $SqlInstances -AvailabilityGroup $instance.AvailabilityGroupName -Database $DatabaseName | Format-Table
83 |
84 | }
85 |
86 | Write-LocalHost -Message 'finished'
87 |
--------------------------------------------------------------------------------
/AlwaysOn_MultiInstance/02_setup_availability_group_demo_01_AgHadr_Endpoint_XESession.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | Script to prepare every instance of SRV1 and SRV2 to build an availability group
3 | But step by step with only a little help from dbatools
4 | And with the choice to run some parts as plain SQL
5 |
6 | Run this script after: 01_setup_instances.ps1
7 |
8 | To update dbatools: Update-Module -Name dbatools
9 | To get the sql server error message: $Error[0].GetBaseException()
10 | #>
11 |
12 | # You can comment this out to use SQL instead when possible
13 | $useDBAtools = $true
14 |
15 |
16 |
17 | $ErrorActionPreference = 'Stop'
18 |
19 | Import-Module -Name dbatools -MinimumVersion 1.0.116
20 |
21 | $sqlInstance2014 = Connect-DbaInstance -SqlInstance 'SRV1\SQL2014', 'SRV2\SQL2014'
22 | $sqlInstance2016 = Connect-DbaInstance -SqlInstance 'SRV1\SQL2016', 'SRV2\SQL2016'
23 | $sqlInstance2017 = Connect-DbaInstance -SqlInstance 'SRV1\SQL2017', 'SRV2\SQL2017'
24 | $sqlInstance2019 = Connect-DbaInstance -SqlInstance 'SRV1\SQL2019', 'SRV2\SQL2019'
25 |
26 | $sqlInstanceAll = $sqlInstance2014 + $sqlInstance2016 + $sqlInstance2017 + $sqlInstance2019
27 |
28 |
29 | # We start with the prerequisites:
30 | # https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/prereqs-restrictions-recommendations-always-on-availability
31 |
32 |
33 | #######
34 | # Task: Enable HADR on every instance
35 | #######
36 |
37 | # https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/enable-and-disable-always-on-availability-groups-sql-server
38 | # To enable HADR, we will use dbatools, that works perfect:
39 | Enable-DbaAgHadr -SqlInstance $sqlInstanceAll -Force | Format-Table
40 |
41 | <# Output:
42 |
43 | ComputerName InstanceName SqlInstance IsHadrEnabled
44 | ------------ ------------ ----------- -------------
45 | SRV1 SQL2014 SRV1\SQL2014 True
46 | SRV2 SQL2014 SRV2\SQL2014 True
47 | SRV1 SQL2016 SRV1\SQL2016 True
48 | SRV2 SQL2016 SRV2\SQL2016 True
49 | SRV1 SQL2017 SRV1\SQL2017 True
50 | SRV2 SQL2017 SRV2\SQL2017 True
51 | SRV1 SQL2019 SRV1\SQL2019 True
52 | SRV2 SQL2019 SRV2\SQL2019 True
53 |
54 | #>
55 |
56 | <# What does Enable-DbaAgHadr do?
57 |
58 | $sqlInstance = Connect-DbaInstance -SqlInstance 'SRV1\SQL2014'
59 | $computerName = $sqlInstance.ComputerName
60 | $instanceName = $sqlInstance.InstanceName
61 | $wmi = New-Object Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer $computerName
62 | $null = $wmi.Initialize()
63 | $sqlService = $wmi.Services | Where-Object DisplayName -EQ "SQL Server ($instanceName)"
64 | $sqlService.ChangeHadrServiceSetting(1)
65 | Invoke-Command -ComputerName $computerName -ScriptBlock { Restart-Service -DisplayName "SQL Server ($using:instanceName)" -Force }
66 |
67 | #>
68 |
69 | # There is no way to do this with SQL - only with Configuration Manager
70 |
71 |
72 | # Now we can create the availability groups
73 |
74 | # The wizard in SSMS can only create an availability group with one or more databases, but I will show here that we can build an empty availability group.
75 | # It will not be in healthy state untill we ad a database, but that's ok for me.
76 |
77 | # Here is the documentation for all the following steps for those who like to do it with SQL:
78 | # https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/create-an-availability-group-transact-sql
79 |
80 | # I will create all the endpoints in advance. Because I need different ports and because I want to show it step by step.
81 | # https://docs.microsoft.com/en-us/sql/t-sql/statements/create-endpoint-transact-sql
82 |
83 | # Differences between SSMS wizard and dbatools:
84 | # SSMS wizard uses "ENCRYPTION = REQUIRED", which is default to "CREATE ENDPOINT" and also used in the Microsoft documentation
85 | # dbatools uses "-EndpointEncryption Supported" when using "New-DbaEndpoint" inside of "Add-DbaAgReplica"
86 | # I will use "ENCRYPTION = REQUIRED" to follow the SSMS wizard
87 |
88 | # The service account is the same in all instances of the lab, so I have this fixed:
89 | $serviceAccount = 'COMPANY\SQLServer'
90 |
91 |
92 | #######
93 | # Task: Create the endpoints and grant permissions to the service account
94 | #######
95 |
96 | if ( $useDBAtools ) {
97 | New-DbaEndpoint -SqlInstance $sqlInstance2014 -Name hadr_endpoint -Type DatabaseMirroring -Protocol Tcp -Role All -EndpointEncryption Required -EncryptionAlgorithm Aes -Port 5022 | Start-DbaEndpoint | Format-Table
98 | New-DbaEndpoint -SqlInstance $sqlInstance2016 -Name hadr_endpoint -Type DatabaseMirroring -Protocol Tcp -Role All -EndpointEncryption Required -EncryptionAlgorithm Aes -Port 5023 | Start-DbaEndpoint | Format-Table
99 | New-DbaEndpoint -SqlInstance $sqlInstance2017 -Name hadr_endpoint -Type DatabaseMirroring -Protocol Tcp -Role All -EndpointEncryption Required -EncryptionAlgorithm Aes -Port 5024 | Start-DbaEndpoint | Format-Table
100 | New-DbaEndpoint -SqlInstance $sqlInstance2019 -Name hadr_endpoint -Type DatabaseMirroring -Protocol Tcp -Role All -EndpointEncryption Required -EncryptionAlgorithm Aes -Port 5025 | Start-DbaEndpoint | Format-Table
101 |
102 | New-DbaLogin -SqlInstance $sqlInstanceAll -Login $serviceAccount | Format-Table
103 |
104 | Grant-DbaAgPermission -SqlInstance $sqlInstanceAll -Login $serviceAccount -Type Endpoint -Permission Connect | Format-Table
105 |
106 | <# Output:
107 |
108 | ComputerName InstanceName SqlInstance ID Name Port EndpointState EndpointType Owner IsAdminEndpoint Fqdn IsSystemObject
109 | ------------ ------------ ----------- -- ---- ---- ------------- ------------ ----- --------------- ---- --------------
110 | SRV1 SQL2014 SRV1\SQL2014 65536 hadr_endpoint 5022 Started DatabaseMirroring sa False TCP://srv1.Company.Pri:5022 False
111 | SRV2 SQL2014 SRV2\SQL2014 65536 hadr_endpoint 5022 Started DatabaseMirroring sa False TCP://srv2.Company.Pri:5022 False
112 |
113 |
114 |
115 | ComputerName InstanceName SqlInstance ID Name Port EndpointState EndpointType Owner IsAdminEndpoint Fqdn IsSystemObject
116 | ------------ ------------ ----------- -- ---- ---- ------------- ------------ ----- --------------- ---- --------------
117 | SRV1 SQL2016 SRV1\SQL2016 65536 hadr_endpoint 5023 Started DatabaseMirroring sa False TCP://srv1.Company.Pri:5023 False
118 | SRV2 SQL2016 SRV2\SQL2016 65536 hadr_endpoint 5023 Started DatabaseMirroring sa False TCP://srv2.Company.Pri:5023 False
119 |
120 |
121 |
122 | ComputerName InstanceName SqlInstance ID Name Port EndpointState EndpointType Owner IsAdminEndpoint Fqdn IsSystemObject
123 | ------------ ------------ ----------- -- ---- ---- ------------- ------------ ----- --------------- ---- --------------
124 | SRV1 SQL2017 SRV1\SQL2017 65536 hadr_endpoint 5024 Started DatabaseMirroring sa False TCP://srv1.Company.Pri:5024 False
125 | SRV2 SQL2017 SRV2\SQL2017 65536 hadr_endpoint 5024 Started DatabaseMirroring sa False TCP://SRV2.Company.Pri:5024 False
126 |
127 |
128 |
129 | ComputerName InstanceName SqlInstance ID Name Port EndpointState EndpointType Owner IsAdminEndpoint Fqdn IsSystemObject
130 | ------------ ------------ ----------- -- ---- ---- ------------- ------------ ----- --------------- ---- --------------
131 | SRV1 SQL2019 SRV1\SQL2019 65536 hadr_endpoint 5025 Started DatabaseMirroring sa False TCP://srv1.Company.Pri:5025 False
132 | SRV2 SQL2019 SRV2\SQL2019 65536 hadr_endpoint 5025 Started DatabaseMirroring sa False TCP://SRV2.Company.Pri:5025 False
133 |
134 |
135 |
136 | ComputerName InstanceName SqlInstance Name LoginType CreateDate LastLogin HasAccess IsLocked IsDisabled
137 | ------------ ------------ ----------- ---- --------- ---------- --------- --------- -------- ----------
138 | SRV1 SQL2014 SRV1\SQL2014 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:45 PM 8/24/2020 6:57:45 PM True False
139 | SRV2 SQL2014 SRV2\SQL2014 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:47 PM 8/24/2020 6:58:11 PM True False
140 | SRV1 SQL2016 SRV1\SQL2016 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:47 PM 8/24/2020 6:58:38 PM True False
141 | SRV2 SQL2016 SRV2\SQL2016 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:49 PM 8/24/2020 6:58:53 PM True False
142 | SRV1 SQL2017 SRV1\SQL2017 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:49 PM 8/24/2020 6:59:12 PM True False
143 | SRV2 SQL2017 SRV2\SQL2017 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:51 PM 8/24/2020 6:59:26 PM True False
144 | SRV1 SQL2019 SRV1\SQL2019 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:51 PM 8/24/2020 6:59:45 PM True False
145 | SRV2 SQL2019 SRV2\SQL2019 COMPANY\SQLServer WindowsUser 8/24/2020 7:17:53 PM 8/24/2020 7:00:05 PM True False
146 |
147 |
148 |
149 | ComputerName InstanceName SqlInstance Name Permission Type Status
150 | ------------ ------------ ----------- ---- ---------- ---- ------
151 | SRV1 SQL2014 SRV1\SQL2014 COMPANY\SQLServer Connect Grant Success
152 | SRV2 SQL2014 SRV2\SQL2014 COMPANY\SQLServer Connect Grant Success
153 | SRV1 SQL2016 SRV1\SQL2016 COMPANY\SQLServer Connect Grant Success
154 | SRV2 SQL2016 SRV2\SQL2016 COMPANY\SQLServer Connect Grant Success
155 | SRV1 SQL2017 SRV1\SQL2017 COMPANY\SQLServer Connect Grant Success
156 | SRV2 SQL2017 SRV2\SQL2017 COMPANY\SQLServer Connect Grant Success
157 | SRV1 SQL2019 SRV1\SQL2019 COMPANY\SQLServer Connect Grant Success
158 | SRV2 SQL2019 SRV2\SQL2019 COMPANY\SQLServer Connect Grant Success
159 |
160 | #>
161 |
162 |
163 | } else {
164 | # How to do it per SQL?
165 |
166 | foreach ( $instance in $sqlInstance2014 ) {
167 | $instance.Query("CREATE ENDPOINT [hadr_endpoint] STATE = STARTED AS TCP (LISTENER_PORT = 5022) FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)")
168 | $instance.Query("CREATE LOGIN [$serviceAccount] FROM WINDOWS")
169 | $instance.Query("GRANT CONNECT ON ENDPOINT::[hadr_endpoint] TO [$serviceAccount]")
170 | }
171 |
172 | foreach ( $instance in $sqlInstance2016 ) {
173 | $instance.Query("CREATE ENDPOINT [hadr_endpoint] STATE = STARTED AS TCP (LISTENER_PORT = 5023) FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)")
174 | $instance.Query("CREATE LOGIN [$serviceAccount] FROM WINDOWS")
175 | $instance.Query("GRANT CONNECT ON ENDPOINT::[hadr_endpoint] TO [$serviceAccount]")
176 | }
177 |
178 | foreach ( $instance in $sqlInstance2017 ) {
179 | $instance.Query("CREATE ENDPOINT [hadr_endpoint] STATE = STARTED AS TCP (LISTENER_PORT = 5024) FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)")
180 | $instance.Query("CREATE LOGIN [$serviceAccount] FROM WINDOWS")
181 | $instance.Query("GRANT CONNECT ON ENDPOINT::[hadr_endpoint] TO [$serviceAccount]")
182 | }
183 |
184 | foreach ( $instance in $sqlInstance2019 ) {
185 | $instance.Query("CREATE ENDPOINT [hadr_endpoint] STATE = STARTED AS TCP (LISTENER_PORT = 5025) FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)")
186 | $instance.Query("CREATE LOGIN [$serviceAccount] FROM WINDOWS")
187 | $instance.Query("GRANT CONNECT ON ENDPOINT::[hadr_endpoint] TO [$serviceAccount]")
188 | }
189 | }
190 |
191 |
192 |
193 | #######
194 | # Task: Configure and start the AlwaysOn_health extended event session
195 | #######
196 |
197 | # This is not done by the dbatools command New-DbaAvailabilityGroup, but it is done by the SSMS wizard - I will do it here
198 |
199 | # https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/always-on-extended-events
200 | # https://www.sqlservercentral.com/blogs/alwayson_health-extended-event-session
201 | # https://www.mssqltips.com/sqlservertip/5287/monitoring-sql-server-availability-groups-with-alwayson-extended-events-health-session/
202 |
203 | if ( $useDBAtools ) {
204 | Get-DbaXESession -SqlInstance $sqlInstanceAll -Session AlwaysOn_health | ForEach-Object -Process { $_.AutoStart = $true ; $_.Alter() ; $_ | Start-DbaXESession } | Format-Table
205 |
206 | <# Output:
207 |
208 | ComputerName InstanceName SqlInstance Name Status StartTime AutoStart State Targets TargetFile Events
209 | ------------ ------------ ----------- ---- ------ --------- --------- ----- ------- ---------- ------
210 | SRV1 SQL2014 SRV1\SQL2014 AlwaysOn_health Running 8/24/2020 7:23:11 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL12.SQL2014\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
211 | SRV2 SQL2014 SRV2\SQL2014 AlwaysOn_health Running 8/24/2020 7:23:14 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL12.SQL2014\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
212 | SRV1 SQL2016 SRV1\SQL2016 AlwaysOn_health Running 8/24/2020 7:23:16 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
213 | SRV2 SQL2016 SRV2\SQL2016 AlwaysOn_health Running 8/24/2020 7:23:24 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
214 | SRV1 SQL2017 SRV1\SQL2017 AlwaysOn_health Running 8/24/2020 7:23:25 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
215 | SRV2 SQL2017 SRV2\SQL2017 AlwaysOn_health Running 8/24/2020 7:23:50 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
216 | SRV1 SQL2019 SRV1\SQL2019 AlwaysOn_health Running 8/24/2020 7:23:51 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL15.SQL2019\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
217 | SRV2 SQL2019 SRV2\SQL2019 AlwaysOn_health Running 8/24/2020 7:23:53 PM True Existing {package0.event_file} {C:\Program Files\Microsoft SQL Server\MSSQL15.SQL2019\MSSQL\Log\AlwaysOn_health.xel} {sqlserver.alwayson_ddl_executed, sqlserver...
218 |
219 | #>
220 |
221 |
222 | <# What does Get-DbaXESession do?
223 |
224 | $smoServer = $sqlInstance2014[0]
225 | $sqlConn = $smoServer.ConnectionContext.SqlConnectionObject
226 | $sqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sqlConn
227 | $xeStore = New-Object Microsoft.SqlServer.Management.XEvent.XEStore $sqlStoreConnection
228 | $xeSession = $xeStore.sessions | Where-Object Name -EQ 'AlwaysOn_health'
229 | $xeSession.AutoStart = $true
230 | $xeSession.Alter()
231 | $xeSession.Start()
232 |
233 | #>
234 |
235 | } else {
236 | # How to do it per SQL?
237 |
238 | foreach ( $instance in $sqlInstanceAll ) {
239 | $instance.Query("ALTER EVENT SESSION [AlwaysOn_health] ON SERVER WITH (STARTUP_STATE = ON)")
240 | $instance.Query("ALTER EVENT SESSION [AlwaysOn_health] ON SERVER STATE = START")
241 | }
242 | }
243 |
244 |
--------------------------------------------------------------------------------
/AlwaysOn_MultiInstance/03_add_SRV3_to_Cluster_and_AvailabilityGroup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [string]$DomainName = 'COMPANY',
4 | [string]$DomainController = 'DOM1',
5 | [string]$NewClusterNode = 'SRV3',
6 | [string]$ClusterName = 'SQLCluster'
7 | )
8 |
9 | function Write-LocalWarning {
10 | param (
11 | [string]$Message
12 | )
13 | Write-Warning -Message ('{0}: {1}' -f (Get-Date), $Message)
14 | }
15 |
16 | function Write-LocalHost {
17 | param (
18 | [string]$Message,
19 | [string]$ForegroundColor = 'Yellow'
20 | )
21 | Microsoft.PowerShell.Utility\Write-Host -Object ('{0}: {1}' -f (Get-Date), $Message) -ForegroundColor $ForegroundColor
22 | }
23 |
24 | function Write-LocalVerbose {
25 | param (
26 | [string]$Message
27 | )
28 | Write-Verbose -Message ('{0}: {1}' -f (Get-Date), $Message)
29 | }
30 |
31 | $ErrorActionPreference = 'Stop'
32 |
33 | Write-LocalHost -Message 'Install cluster freature on new node'
34 | $installResult = Invoke-Command -ComputerName $NewClusterNode -ScriptBlock { Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools }
35 | $installResult | Format-Table
36 | if ( $installResult.RestartNeeded -eq 'Yes' ) {
37 | # Restart is needed on Windows Server 2019
38 | Restart-Computer -ComputerName $NewClusterNode
39 | Start-Sleep -Seconds 60
40 | }
41 |
42 | Write-LocalHost -Message 'Install cluster freature on new node'
43 | Get-Cluster -Name $ClusterName | Add-ClusterNode -Name $NewClusterNode
44 | # This should output something, but it does not
45 |
46 | Write-LocalHost -Message 'Lets see if we have a new node'
47 | Get-Cluster -Name $ClusterName | Get-ClusterNode
48 | # Yes, we have
49 |
50 |
51 | # put code in here from: SRV3_MultiInstance\setup_instances.ps1
52 |
53 |
54 | # plan:
55 |
56 | # Add replica (delete database AdventureWorks befor that - or not?)
57 | # Add databases (maybe they are added automatically with automatic seeding?
58 |
59 |
60 | Write-LocalHost -Message 'finished'
61 |
--------------------------------------------------------------------------------
/AutomatedLab/01_Install_HyperV.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Stop'
2 |
3 | # Change execution policy to remote signed if needed:
4 | #####################################################
5 |
6 | $currentPolicy = Get-ExecutionPolicy
7 | if ($currentPolicy -ne 'RemoteSigned') {
8 | Write-Warning -Message "ExecutionPolicy is currently $currentPolicy, so we change it to RemoteSigned"
9 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force
10 | }
11 |
12 | <# Just for information:
13 |
14 | Get-ExecutionPolicy -List
15 |
16 | Scope ExecutionPolicy
17 | ----- ---------------
18 | MachinePolicy Undefined
19 | UserPolicy Undefined
20 | Process Undefined
21 | CurrentUser Undefined
22 | LocalMachine RemoteSigned
23 |
24 | #>
25 |
26 |
27 | # Enable Hyper-V (restart needed):
28 | ##################################
29 |
30 | $currentHyperV = Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Online
31 | if ($currentHyperV.State -ne 'Enabled') {
32 | Write-Warning -Message "State of Microsoft-Hyper-V is currently $($currentHyperV.State), so we enable it and restart the computer"
33 | Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -All -Online -NoRestart
34 | Restart-Computer -Force
35 | }
36 |
--------------------------------------------------------------------------------
/AutomatedLab/02_Install_AutomatedLab.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Stop'
2 |
3 | # Setup PSGallery as a trusted source for PowerShell modules:
4 | #############################################################
5 |
6 | # Maybe needed for older systems that don't use TLS 1.2:
7 | # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
8 | $null = Install-PackageProvider -Name Nuget -Force
9 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
10 |
11 |
12 | # Install and configure the PowerShell module AutomatedLab:
13 | ###########################################################
14 |
15 | Install-Module -Name AutomatedLab -AllowClobber -SkipPublisherCheck -Force
16 | Install-Module -Name Posh-SSH
17 |
18 | [Environment]::SetEnvironmentVariable('AUTOMATEDLAB_TELEMETRY_OPTIN', 'false', 'Machine')
19 | $env:AUTOMATEDLAB_TELEMETRY_OPTIN = 'false'
20 |
21 | Set-PSFConfig -Module AutomatedLab -Name LabSourcesLocation -Description 'Location of lab sources folder' -Validation string -Value 'C:\AutomatedLab-Sources' -PassThru | Register-PSFConfig
22 | Set-PSFConfig -Module AutomatedLab -Name VmPath -Description 'Location of lab vm folder' -Validation string -Value 'C:\AutomatedLab-VMs' -PassThru | Register-PSFConfig
23 |
24 | Import-Module -Name AutomatedLab
25 |
26 | New-LabSourcesFolder
27 |
28 | Enable-LabHostRemoting -Force
29 |
30 |
31 | # Download some operating system ISOs:
32 | ######################################
33 |
34 | $isoList = @(
35 | @{
36 | Uri = 'https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso'
37 | OutFile = "$labSources\ISOs\2022_x64_EN_Eval.iso"
38 | }
39 | <#
40 | @{
41 | Uri = 'https://software-download.microsoft.com/download/sg/17763.253.190108-0006.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso'
42 | OutFile = "$labSources\ISOs\2019_x64_EN_Eval.iso"
43 | }
44 | @{
45 | Uri = 'https://software-static.download.prss.microsoft.com/dbazure/988969d5-f34g-4e03-ac9d-1f9786c66751/22621.525.220925-0207.ni_release_svc_refresh_CLIENTENTERPRISEEVAL_OEMRET_x64FRE_en-us.iso'
46 | OutFile = "$labSources\ISOs\WIN11_x64_ENT_22H2_EN_Eval.iso"
47 | }
48 | @{
49 | Uri = 'https://software-download.microsoft.com/download/sg/444969d5-f34g-4e03-ac9d-1f9786c69161/19044.1288.211006-0501.21h2_release_svc_refresh_CLIENTENTERPRISEEVAL_OEMRET_x64FRE_en-us.iso'
50 | OutFile = "$labSources\ISOs\WIN10_x64_ENT_21H2_EN_Eval.iso"
51 | }
52 | #>
53 | @{
54 | Uri = 'https://mirror1.hs-esslingen.de/pub/Mirrors/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2207-02.iso'
55 | OutFile = "$labSources\ISOs\CentOS-7-x86_64-DVD-2207-02.iso"
56 | }
57 | <#
58 | @{
59 | Uri = 'https://mirror1.hs-esslingen.de/pub/Mirrors/centos-stream/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso'
60 | OutFile = "$labSources\ISOs\CentOS-Stream-9-latest-x86_64-dvd1.iso"
61 | }
62 | @{
63 | Uri = 'https://download.opensuse.org/distribution/leap/15.2/iso/openSUSE-Leap-15.2-DVD-x86_64.iso'
64 | OutFile = "$labSources\ISOs\openSUSE-Leap-15.2-DVD-x86_64.iso"
65 | }
66 | #>
67 | )
68 | # You can find more windows operating systems here: https://github.com/VirtualEngine/Lability/blob/dev/Config/Media.json
69 | # The link for Windows 11 is from here: https://github.com/VirtualEngine/Lability/pull/415/files
70 |
71 | foreach ($iso in $isoList) {
72 | if (-not (Test-Path -Path $iso.OutFile)) {
73 | Invoke-WebRequest -Uri $iso.Uri -OutFile $iso.OutFile -UseBasicParsing
74 | }
75 | }
76 |
77 | Get-LabAvailableOperatingSystem | Sort-Object OperatingSystemName
78 |
79 | <#
80 |
81 | OperatingSystemName Idx Version PublishedDate IsoPath
82 | ------------------- --- ------- ------------- -------
83 | CentOS Stream 9 0 9.0 12.12.2022 11:04:22 C:\AutomatedLab-Sources\ISOs\CentOS-Stream-9-latest-x86_64-...
84 | CentOS-7 0 7.0 26.07.2022 16:40:33 C:\AutomatedLab-Sources\ISOs\CentOS-7-x86_64-DVD-2207-02.iso
85 | openSUSE Leap 15.2 0 15.0 09.06.2020 17:18:51 C:\AutomatedLab-Sources\ISOs\openSUSE-Leap-15.2-DVD-x86_64.iso
86 | Windows 10 Enterprise Evaluation 1 10.0.19041.1288 06.10.2021 15:07:52 C:\AutomatedLab-Sources\ISOs\WIN10_x64_ENT_21H2_EN_Eval.iso
87 | Windows 11 Enterprise Evaluation 1 10.0.22621.525 25.09.2022 03:47:56 C:\AutomatedLab-Sources\ISOs\WIN11_x64_ENT_22H2_EN_Eval.iso
88 | Windows Server 2019 Datacenter Evaluation 3 10.0.17763.253 08.01.2019 02:26:26 C:\AutomatedLab-Sources\ISOs\2019_x64_EN_Eval.iso
89 | Windows Server 2019 Datacenter Evaluation (Desktop Experience) 4 10.0.17763.253 08.01.2019 02:27:56 C:\AutomatedLab-Sources\ISOs\2019_x64_EN_Eval.iso
90 | Windows Server 2019 Standard Evaluation 1 10.0.17763.253 08.01.2019 02:29:22 C:\AutomatedLab-Sources\ISOs\2019_x64_EN_Eval.iso
91 | Windows Server 2019 Standard Evaluation (Desktop Experience) 2 10.0.17763.253 08.01.2019 02:27:51 C:\AutomatedLab-Sources\ISOs\2019_x64_EN_Eval.iso
92 | Windows Server 2022 Datacenter Evaluation 3 10.0.20348.587 03.03.2022 05:03:12 C:\AutomatedLab-Sources\ISOs\2022_x64_EN_Eval.iso
93 | Windows Server 2022 Datacenter Evaluation (Desktop Experience) 4 10.0.20348.587 03.03.2022 05:10:29 C:\AutomatedLab-Sources\ISOs\2022_x64_EN_Eval.iso
94 | Windows Server 2022 Standard Evaluation 1 10.0.20348.587 03.03.2022 05:02:13 C:\AutomatedLab-Sources\ISOs\2022_x64_EN_Eval.iso
95 | Windows Server 2022 Standard Evaluation (Desktop Experience) 2 10.0.20348.587 03.03.2022 05:08:50 C:\AutomatedLab-Sources\ISOs\2022_x64_EN_Eval.iso
96 |
97 | #>
98 |
--------------------------------------------------------------------------------
/AutomatedLab/03_Setup_TestLab.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Continue'
2 |
3 | Import-Module -Name AutomatedLab
4 | Import-Module -Name Posh-SSH
5 |
6 | $LabName = 'TestLab'
7 | $LabNetworkBase = '192.168.123'
8 |
9 | $LabAdminUser = 'User'
10 | $LabAdminPassword = 'Passw0rd!'
11 |
12 | # If you disable the following line, only Windows and Linux will be deployed (which is a faster test)
13 | $LabDomainName = 'testlab.local'
14 |
15 | # If you disable the following line, we try to retrieve the currently used dns server address
16 | #$LabDnsServer = '1.1.1.1'
17 |
18 |
19 | $MachineDefinitionDefaults = @{
20 | Processors = 2
21 | Memory = 2GB
22 | Network = $LabName
23 | Gateway = "$LabNetworkBase.1"
24 | TimeZone = 'W. Europe Standard Time'
25 | }
26 |
27 | $MachineDefinition = @(
28 | @{
29 | Name = 'DC'
30 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
31 | IpAddress = "$LabNetworkBase.10"
32 | Roles = 'RootDC'
33 | }
34 | @{
35 | Name = 'Windows'
36 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
37 | IpAddress = "$LabNetworkBase.20"
38 | }
39 | @{
40 | Name = 'Linux'
41 | OperatingSystem = 'CentOS-7'
42 | IpAddress = "$LabNetworkBase.30"
43 | }
44 | )
45 |
46 |
47 | $ChocolateyPackages = @(
48 | 'powershell-core'
49 | 'notepadplusplus'
50 | '7zip'
51 | 'vscode'
52 | 'vscode-powershell'
53 | 'googlechrome'
54 | )
55 |
56 | $PowerShellModules = @(
57 | 'PSFramework'
58 | 'ImportExcel'
59 | 'Posh-SSH'
60 | 'dbatools'
61 | )
62 |
63 | $DockerRunCommands = @(
64 | # "docker run --name SQLServer --memory=2g -p 1433:1433 -e MSSQL_SA_PASSWORD='$LabAdminPassword' -e ACCEPT_EULA=Y -e MSSQL_PID=Express --detach --restart always mcr.microsoft.com/mssql/server:2019-latest"
65 | # "docker run --name Oracle --memory=3g -p 1521:1521 -e ORACLE_PWD='$LabAdminPassword' -e ORACLE_CHARACTERSET=AL32UTF8 --detach --restart always container-registry.oracle.com/database/express:latest"
66 | "docker run --name MySQL --memory=1g -p 3306:3306 -e MYSQL_ROOT_PASSWORD='$LabAdminPassword' --detach --restart always mysql:latest"
67 | # As an alternative for MySQL:
68 | # "docker run --name MariaDB --memory=1g -p 3306:3306 -e MARIADB_ROOT_PASSWORD='$LabAdminPassword' --detach --restart always mariadb:latest"
69 | # "docker run --name PostgreSQL --memory=1g -p 5432:5432 -e POSTGRES_PASSWORD='$LabAdminPassword' --detach --restart always postgres:latest"
70 | # As an alternative for PostgreSQL:
71 | # "docker run --name PostGIS --memory=1g -p 5432:5432 -e POSTGRES_PASSWORD='$LabAdminPassword' --detach --restart always postgres:latest"
72 | )
73 |
74 |
75 | ### End of configuration ###
76 |
77 |
78 | Set-PSFConfig -Module AutomatedLab -Name DoNotWaitForLinux -Value $true
79 |
80 | New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV
81 | Set-LabInstallationCredential -Username $LabAdminUser -Password $LabAdminPassword
82 | if ($LabDomainName) {
83 | Add-LabDomainDefinition -Name $LabDomainName -AdminUser $LabAdminUser -AdminPassword $LabAdminPassword
84 | $MachineDefinitionDefaults.DomainName = $LabDomainName
85 | } else {
86 | $MachineDefinition = $MachineDefinition | Where-Object Roles -NotContains RootDC
87 | if ($LabDnsServer) {
88 | $dnsServer1 = $LabDnsServer
89 | } else {
90 | $dnsServer1 = Get-NetAdapter |
91 | Where-Object { $_.Status -eq 'Up' -and $_.Name -notlike 'vEthernet*' } |
92 | Get-DnsClientServerAddress -AddressFamily IPv4 |
93 | Select-Object -ExpandProperty ServerAddresses -First 1
94 | }
95 | $MachineDefinitionDefaults.DnsServer1 = $dnsServer1
96 | }
97 | Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace "$LabNetworkBase.0/24"
98 | foreach ($md in $MachineDefinition) {
99 | # $md = $MachineDefinition[0]
100 | $lmd = @{ }
101 | foreach ($key in $MachineDefinitionDefaults.Keys) {
102 | $lmd.$key = $MachineDefinitionDefaults.$key
103 | }
104 | foreach ($key in $md.Keys) {
105 | $lmd.$key = $md.$key
106 | }
107 | $lmd.ResourceName = "$LabName-$($md.Name)"
108 | Add-LabMachineDefinition @lmd
109 | }
110 | Install-Lab -NoValidation
111 |
112 | $null = New-NetNat -Name $LabName -InternalIPInterfaceAddressPrefix "$LabNetworkBase.0/24"
113 |
114 | Start-LabVM -ComputerName Linux
115 |
116 | Invoke-LabCommand -ComputerName (Get-LabVM) -ActivityName 'Disable Windows updates' -ScriptBlock {
117 | # https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings
118 | Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1
119 | }
120 |
121 | Invoke-LabCommand -ComputerName (Get-LabVM) -ActivityName 'Setting my favorite explorer settings' -ScriptBlock {
122 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name HideFileExt -Value 0
123 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneShowAllFolders -Value 1
124 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneExpandToCurrentFolder -Value 1
125 | }
126 |
127 | $pingSucceeded = Invoke-LabCommand -ComputerName Windows -ActivityName 'Testing internet access' -PassThru -ScriptBlock {
128 | (Test-NetConnection -ComputerName www.google.de -WarningAction SilentlyContinue).PingSucceeded
129 | }
130 |
131 | if (-not $pingSucceeded) {
132 | Write-Warning -Message "We don't have internet access, but let's wait for 30 seconds and try again"
133 | Start-Sleep -Seconds 30
134 | $pingSucceeded = Invoke-LabCommand -ComputerName Windows -ActivityName 'Testing internet access' -PassThru -ScriptBlock {
135 | (Test-NetConnection -ComputerName www.google.de -WarningAction SilentlyContinue).PingSucceeded
136 | }
137 | if (-not $pingSucceeded) {
138 | Write-Warning -Message "We don't have internet access, so stopping here"
139 | break
140 | }
141 | }
142 |
143 | Invoke-LabCommand -ComputerName Windows -ActivityName 'Installing chocolatey packages' -ArgumentList @(, $ChocolateyPackages) -ScriptBlock {
144 | param($ChocolateyPackages)
145 |
146 | $ErrorActionPreference = 'Stop'
147 |
148 | $logPath = 'C:\DeployDebug\InstallChocolateyPackages.log'
149 |
150 | try {
151 | Invoke-Expression -Command ([System.Net.WebClient]::new().DownloadString('https://chocolatey.org/install.ps1')) *>$logPath
152 | $installResult = choco install $ChocolateyPackages --confirm --limitoutput --no-progress *>&1
153 | if ($installResult -match 'Warnings:') {
154 | Write-Warning -Message 'Chocolatey generated warnings'
155 | }
156 | $info = $installResult -match 'Chocolatey installed (\d+)/(\d+) packages' | Select-Object -First 1
157 | if ($info -match 'Chocolatey installed (\d+)/(\d+) packages') {
158 | if ($Matches[1] -ne $Matches[2]) {
159 | Write-Warning -Message "Chocolatey only installed $($Matches[1]) of $($Matches[2]) packages"
160 | $installResult | Add-Content -Path $logPath
161 | }
162 | } else {
163 | Write-Warning -Message "InstallResult: $installResult"
164 | }
165 | } catch {
166 | $message = "Setting up Chocolatey failed: $_"
167 | $message | Add-Content -Path $logPath
168 | Write-Warning -Message $message
169 | }
170 | }
171 |
172 | Invoke-LabCommand -ComputerName Windows -ActivityName 'Installing PowerShell modules' -ArgumentList @(, $PowerShellModules) -ScriptBlock {
173 | param($PowerShellModules)
174 |
175 | $logPath = 'C:\DeployDebug\InstallPowerShellModules.log'
176 |
177 | $ErrorActionPreference = 'Stop'
178 |
179 | try {
180 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
181 | if ((Get-PackageProvider -ListAvailable).Name -notcontains 'Nuget') {
182 | $null = Install-PackageProvider -Name Nuget -Force
183 | 'Install-PackageProvider ok' | Add-Content -Path $logPath
184 | } else {
185 | 'Install-PackageProvider not needed' | Add-Content -Path $logPath
186 | }
187 | if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
188 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
189 | 'Set-PSRepository ok' | Add-Content -Path $logPath
190 | } else {
191 | 'Set-PSRepository not needed' | Add-Content -Path $logPath
192 | }
193 | foreach ($name in $PowerShellModules) {
194 | if (-not (Get-Module -Name $name -ListAvailable)) {
195 | Install-Module -Name $name
196 | "Install-Module $name ok" | Add-Content -Path $logPath
197 | } else {
198 | "Install-Module $name not needed" | Add-Content -Path $logPath
199 | }
200 | }
201 | } catch {
202 | $message = "Setting up PowerShell failed: $_"
203 | $message | Add-Content -Path $logPath
204 | Write-Warning -Message $message
205 | }
206 | }
207 |
208 | Invoke-LabCommand -ComputerName Windows -ActivityName 'Downloading PowerShell-for-DBAs' -ScriptBlock {
209 | $logPath = 'C:\DeployDebug\InstallPowerShell-for-DBAs.log'
210 |
211 | $ErrorActionPreference = 'Stop'
212 |
213 | try {
214 | $null = New-Item -Path C:\GitHub -ItemType Directory
215 |
216 | Invoke-WebRequest -Uri https://github.com/andreasjordan/PowerShell-for-DBAs/archive/refs/heads/main.zip -OutFile C:\GitHub\main.zip -UseBasicParsing
217 | Expand-Archive -Path C:\GitHub\main.zip -DestinationPath C:\GitHub
218 | Rename-Item C:\GitHub\PowerShell-for-DBAs-main -NewName PowerShell-for-DBAs
219 | Remove-Item C:\GitHub\main.zip
220 |
221 | $null = New-Item -Path C:\NuGet -ItemType Directory
222 | foreach ($package in 'Oracle.ManagedDataAccess', 'Oracle.ManagedDataAccess.Core', 'MySql.Data', 'Npgsql', 'Microsoft.Extensions.Logging.Abstractions') {
223 | Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/$package -OutFile C:\NuGet\package.zip -UseBasicParsing
224 | Expand-Archive -Path C:\NuGet\package.zip -DestinationPath C:\NuGet\$package
225 | Remove-Item -Path C:\NuGet\package.zip
226 | }
227 | } catch {
228 | $message = "Setting up files for PowerShell-for-DBAs failed: $_"
229 | $message | Add-Content -Path $logPath
230 | Write-Warning -Message $message
231 | }
232 | }
233 |
234 | Write-PSFMessage -Level Host -Message "Setup of Windows finished, now we wait for Linux to be reachable"
235 | $rootCredential = [PSCredential]::new('root', (ConvertTo-SecureString -String $LabAdminPassword -AsPlainText -Force))
236 | $linuxIp = ($MachineDefinition | Where-Object Name -eq Linux).IpAddress
237 | while ($true) {
238 | try {
239 | $sshSession = New-SSHSession -ComputerName $linuxIp -Credential $rootCredential -Force -WarningAction SilentlyContinue -ErrorAction Stop
240 | break
241 | } catch {
242 | Start-Sleep -Seconds 10
243 | }
244 | }
245 |
246 | Write-PSFMessage -Level Host -Message "Installing docker on Linux"
247 | $sshCommands = @(
248 | 'yum -y update'
249 | 'yum install -y yum-utils'
250 | 'yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo'
251 | 'yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
252 | 'systemctl enable --now docker'
253 | )
254 | foreach ($cmd in $sshCommands) {
255 | $sshResult = Invoke-SSHCommand -SSHSession $sshSession -Command $cmd -TimeOut 600
256 | if ($sshResult.ExitStatus -gt 0) {
257 | Write-PSFMessage -Level Warning -Message "Command '$cmd' returned with ExitStatus $($sshResult.ExitStatus)"
258 | break
259 | }
260 | }
261 |
262 | Write-PSFMessage -Level Host -Message "Starting databases on docker"
263 | foreach ($cmd in $DockerRunCommands) {
264 | $containerName = $cmd -replace '^.*--name ([^ ]+).*$', '$1'
265 | Write-PSFMessage -Level Host -Message "Starting docker container $containerName"
266 | $null = Invoke-SSHCommand -SSHSession $sshSession -Command $cmd -TimeOut 36000
267 | }
268 |
269 | Write-PSFMessage -Level Host -Message "finished"
270 |
271 |
272 | break
273 |
274 |
275 | vmconnect.exe localhost $LabName-Windows
276 |
277 | Remove-Lab -Name $LabName -Confirm:$false ; Get-NetNat -Name $LabName -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
278 |
279 |
--------------------------------------------------------------------------------
/AutomatedLab/04_Download_CustomAssets.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Stop'
2 |
3 | Import-Module -Name AutomatedLab
4 |
5 | # Download the GitHub repository and move CustomAssets and CustomScripts to LabSources directory:
6 | #################################################################################################
7 |
8 | $tmpFolder = Join-Path -Path $env:TEMP -ChildPath (New-Guid).Guid
9 | $null = New-Item -Path $tmpFolder -ItemType Directory
10 | $uri = 'https://github.com/andreasjordan/demos/archive/refs/heads/master.zip'
11 | Invoke-WebRequest -Uri $uri -OutFile "$tmpFolder\master.zip" -UseBasicParsing
12 | Expand-Archive -Path "$tmpFolder\master.zip" -DestinationPath $tmpFolder
13 | Move-Item -Path "$tmpFolder\demos-master\AutomatedLab\CustomAssets" -Destination $labSources
14 | Move-Item -Path "$tmpFolder\demos-master\AutomatedLab\CustomScripts" -Destination $labSources
15 | Remove-Item -Path $tmpFolder -Recurse -Force
16 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/DockerImages/README.md:
--------------------------------------------------------------------------------
1 | Exported Docker images are located here so that a download of this data is not necessary as part of `docker pull` or `docker run`.
2 |
3 | The donwload of the Oracle image in particular takes a very long time, so a local copy speeds up the setup of the lab considerably.
4 |
5 | TODO: Share the code to export the images here...
6 |
7 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/FileServerISOs/README.md:
--------------------------------------------------------------------------------
1 | ISO files are located here, the contents of which can be copied to a lab's file server.
2 |
3 | File | Size | Description
4 | :------------------------------------------------ | -------: | :-------------------------
5 | en_sql_server_2017_developer_x64_dvd_11296168.iso | 1.475 MB | Microsoft SQL Server 2017
6 | en_sql_server_2019_developer_x64_dvd_e5ade34a.iso | 1.361 MB | Microsoft SQL Server 2019
7 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/manifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/Backup.xml:
--------------------------------------------------------------------------------
1 |
2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/DomainSysvol/GPO/Machine/comment.cmtx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/DomainSysvol/GPO/Machine/registry.pol:
--------------------------------------------------------------------------------
1 | PReg [ S o f t w a r e \ P o l i c i e s \ M i c r o s o f t \ W i n d o w s \ W i n R M \ S e r v i c e ; A l l o w C r e d S S P ; ; ; ]
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/bkupInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/gpreport.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_COMP_CredSSP_Server/{A30D2B62-F106-40AD-9C6F-6E410751F850}/gpreport.xml
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/manifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/Backup.xml:
--------------------------------------------------------------------------------
1 |
2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/DomainSysvol/GPO/Machine/microsoft/windows nt/SecEdit/GptTmpl.inf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/DomainSysvol/GPO/Machine/microsoft/windows nt/SecEdit/GptTmpl.inf
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/bkupInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/gpreport.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PasswordAge_0/{9900D0B7-C5F0-414E-80D4-9BF07C4CC8C0}/gpreport.xml
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/manifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/Backup.xml:
--------------------------------------------------------------------------------
1 |
2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/DomainSysvol/GPO/Machine/comment.cmtx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/DomainSysvol/GPO/Machine/registry.pol:
--------------------------------------------------------------------------------
1 | PReg [ S o f t w a r e \ P o l i c i e s \ M i c r o s o f t \ P o w e r \ P o w e r S e t t i n g s ; A c t i v e P o w e r S c h e m e ; ; J ; 8 c 5 e 7 f d a - e 8 b f - 4 a 9 6 - 9 a 8 5 - a 6 e 2 3 a 8 c 6 3 5 c ]
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/bkupInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/gpreport.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_COMP_PowerPlan_HighPerformance/{B69913CD-5BFA-4CCA-8A5E-258B50F4E5A8}/gpreport.xml
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/manifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/{5CB66590-4C6F-4196-AC68-C9F028B5A7CB}/Backup.xml:
--------------------------------------------------------------------------------
1 |
2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/{5CB66590-4C6F-4196-AC68-C9F028B5A7CB}/DomainSysvol/GPO/User/Preferences/Drives/Drives.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/{5CB66590-4C6F-4196-AC68-C9F028B5A7CB}/bkupInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/{5CB66590-4C6F-4196-AC68-C9F028B5A7CB}/gpreport.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_USR_DriveMapping_F_FileServer/{5CB66590-4C6F-4196-AC68-C9F028B5A7CB}/gpreport.xml
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/manifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/{DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}/Backup.xml:
--------------------------------------------------------------------------------
1 |
2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 0a 7c 75 fe 85 da 01 40 31 ed e5 79 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/{DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}/DomainSysvol/GPO/User/Preferences/Registry/Registry.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/{DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}/bkupInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/{DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}/gpreport.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasjordan/demos/96a0b6df259ae3ce4c1156d6aa2662668f6fd12a/AutomatedLab/CustomAssets/GPOs/GPO_USR_Explorer/{DC1CA3A6-3D05-42C2-9A55-EE7C8ECE1919}/gpreport.xml
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/SampleDatabases/README.md:
--------------------------------------------------------------------------------
1 | Here are sample databases that can be copied to a lab's file server.
2 |
3 |
4 | File | Size | Description
5 | :----------------------------------- | -------: | :--------------------------------------------------------
6 | AdventureWorks2012.bak | 45 MB | AdventureWorks for SQL Server 2012
7 | AdventureWorks2014.bak | 45 MB | AdventureWorks for SQL Server 2014
8 | AdventureWorks2016.bak | 46 MB | AdventureWorks for SQL Server 2016
9 | AdventureWorks2017.bak | 48 MB | AdventureWorks for SQL Server 2017
10 | StackOverflowORDIXNewData.bak | 410 MB | StackOverflow in a special ORDIX version (additional data)
11 | StackOverflowORDIXWithoutNewData.bak | 1.362 MB | StackOverflow in a special ORDIX version (Basic version)
12 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomAssets/Software/README.md:
--------------------------------------------------------------------------------
1 | Here are software packages that can be copied to a lab's file server.
2 |
3 |
4 | File | Size | Description
5 | :----------------------------------- | -------: | :------------------------------------------------
6 | OracleXE213_Win64.zip | 1.876 MB | Oracle Database 21.3 Express Edition for Windows
7 | sqldeveloper-22.2.1.234.1810-x64.zip | 444 MB | Oracle SQL Developer 22.2 for Windows
8 | SQLEXPR_x64_ENU.exe | 256 MB | Microsoft SQL Server 2019 Express Edition
9 | WINDOWS.X64_193000_client.zip | 995 MB | Oracle Client 19.3 for Windows
10 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomScripts/Docker_Databases.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Continue'
2 |
3 | Import-Module -Name AutomatedLab
4 | Import-Module -Name Posh-SSH
5 |
6 | $LabName = 'DockerDatabases'
7 | $LabNetworkBase = '192.168.123'
8 |
9 | $LabAdminUser = 'User'
10 | $LabAdminPassword = 'Passw0rd!'
11 |
12 | $MachineDefinition = @{
13 | Name = $LabName
14 | OperatingSystem = 'CentOS-7'
15 | Processors = 4
16 | Network = $LabName
17 | IpAddress = "$LabNetworkBase.10"
18 | Gateway = "$LabNetworkBase.1"
19 | DnsServer1 = '1.1.1.1'
20 | TimeZone = 'W. Europe Standard Time'
21 | }
22 |
23 | <# Some commands that I use for importing, removing, stopping, starting or connecting to the lab:
24 |
25 | Import-Lab -Name $LabName -NoValidation
26 |
27 | Remove-Lab -Name $LabName -Confirm:$false; Get-NetNat -Name $LabName -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
28 |
29 | Stop-VM -Name $MachineDefinition.Name
30 | Start-VM -Name $MachineDefinition.Name
31 |
32 | #>
33 |
34 | $password = $LabAdminPassword
35 | $hostname = 'localhost'
36 |
37 | $DatabaseDefinition = @(
38 | [PSCustomObject]@{
39 | ContainerName = 'SQLServer'
40 | ContainerImage = 'mcr.microsoft.com/mssql/server:2022-latest'
41 | ContainerMemoryGB = 2
42 | ContainerConfig = "-p 1433:1433 -e MSSQL_SA_PASSWORD='$password' -e ACCEPT_EULA=Y -e MSSQL_PID=Express"
43 | Instance = $hostname
44 | AdminPassword = $password
45 | SqlQueries = @(
46 | "CREATE LOGIN StackOverflow WITH PASSWORD = '$password', CHECK_POLICY = OFF"
47 | 'CREATE DATABASE StackOverflow'
48 | 'ALTER AUTHORIZATION ON DATABASE::StackOverflow TO StackOverflow'
49 | )
50 | }
51 | [PSCustomObject]@{
52 | ContainerName = 'Oracle'
53 | ContainerImage = 'container-registry.oracle.com/database/express:latest'
54 | ContainerMemoryGB = 3
55 | ContainerConfig = "-p 1521:1521 -e ORACLE_PWD='$password' -e ORACLE_CHARACTERSET=AL32UTF8"
56 | Instance = "$hostname/XEPDB1"
57 | AdminPassword = $password
58 | SqlQueries = @(
59 | 'CREATE USER stackoverflow IDENTIFIED BY "{0}" DEFAULT TABLESPACE users QUOTA UNLIMITED ON users TEMPORARY TABLESPACE temp' -f $password
60 | 'GRANT CREATE SESSION TO stackoverflow'
61 | 'GRANT ALL PRIVILEGES TO stackoverflow'
62 | 'CREATE USER geodemo IDENTIFIED BY "{0}" DEFAULT TABLESPACE users QUOTA UNLIMITED ON users TEMPORARY TABLESPACE temp' -f $password
63 | 'GRANT CREATE SESSION TO geodemo'
64 | 'GRANT ALL PRIVILEGES TO geodemo'
65 | )
66 | }
67 | [PSCustomObject]@{
68 | ContainerName = 'MySQL'
69 | ContainerImage = 'mysql:latest'
70 | ContainerMemoryGB = 1
71 | ContainerConfig = "-p 3306:3306 -e MYSQL_ROOT_PASSWORD='$password'"
72 | Instance = $hostname
73 | AdminPassword = $password
74 | SqlQueries = @(
75 | #'SET GLOBAL local_infile=1'
76 | 'SET PERSIST local_infile=1'
77 | "CREATE USER 'stackoverflow'@'%' IDENTIFIED BY '$password'"
78 | 'CREATE DATABASE stackoverflow'
79 | "GRANT ALL PRIVILEGES ON stackoverflow.* TO 'stackoverflow'@'%'"
80 | )
81 | }
82 | [PSCustomObject]@{
83 | ContainerName = 'MariaDB'
84 | ContainerImage = 'mariadb:latest'
85 | ContainerMemoryGB = 1
86 | ContainerConfig = "-p 13306:3306 -e MARIADB_ROOT_PASSWORD='$password'"
87 | Instance = "$($hostname):13306"
88 | AdminPassword = $password
89 | SqlQueries = @(
90 | "CREATE USER 'stackoverflow'@'%' IDENTIFIED BY '$password'"
91 | 'CREATE DATABASE stackoverflow'
92 | "GRANT ALL PRIVILEGES ON stackoverflow.* TO 'stackoverflow'@'%'"
93 | )
94 | }
95 | [PSCustomObject]@{
96 | ContainerName = 'PostgreSQL'
97 | ContainerImage = 'postgres:latest'
98 | ContainerMemoryGB = 1
99 | ContainerConfig = "-p 5432:5432 -e POSTGRES_PASSWORD='$password'"
100 | Instance = $hostname
101 | AdminPassword = $password
102 | SqlQueries = @(
103 | "CREATE USER stackoverflow WITH PASSWORD '$password'"
104 | 'CREATE DATABASE stackoverflow WITH OWNER stackoverflow'
105 | )
106 | }
107 | [PSCustomObject]@{
108 | ContainerName = 'PostGIS'
109 | ContainerImage = 'postgis/postgis'
110 | ContainerMemoryGB = 1
111 | ContainerConfig = "-p 15432:5432 -e POSTGRES_PASSWORD='$password'"
112 | Instance = "$($hostname):15432"
113 | AdminPassword = $password
114 | SqlQueries = @(
115 | "CREATE USER geodemo WITH PASSWORD '$password'"
116 | 'CREATE DATABASE geodemo WITH OWNER geodemo'
117 | '\connect geodemo'
118 | 'CREATE EXTENSION postgis'
119 | )
120 | }
121 | )
122 |
123 | # $DatabaseDefinition = $DatabaseDefinition | Where-Object ContainerName -in SQLServer, Oracle
124 | $DatabaseDefinition = $DatabaseDefinition | Out-GridView -Title 'Select docker conatiners to start' -OutputMode Multiple
125 |
126 |
127 | ### End of configuration ###
128 |
129 |
130 | $MachineDefinition.Memory = 2GB + ($DatabaseDefinition.ContainerMemoryGB | Measure-Object -Sum).Sum * 1GB
131 |
132 | Set-PSFConfig -Module AutomatedLab -Name DoNotWaitForLinux -Value $true
133 |
134 | New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV
135 | Set-LabInstallationCredential -Username $LabAdminUser -Password $LabAdminPassword
136 | Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace "$LabNetworkBase.0/24"
137 | Add-LabMachineDefinition @MachineDefinition
138 | Install-Lab -NoValidation
139 |
140 | $null = New-NetNat -Name $LabName -InternalIPInterfaceAddressPrefix "$LabNetworkBase.0/24"
141 |
142 | Start-LabVM -ComputerName $MachineDefinition.Name
143 |
144 | # Now the linux maschine is started and will be installed automatically. We just have to wait...
145 |
146 |
147 | Write-PSFMessage -Level Host -Message "Waiting until we can connect and open ssh and sftp session"
148 | $rootCredential = [PSCredential]::new('root', (ConvertTo-SecureString -String $LabAdminPassword -AsPlainText -Force))
149 | $linuxIp = $MachineDefinition.IpAddress
150 | while ($true) {
151 | try {
152 | if (-not $sshSession) {
153 | $sshSession = New-SSHSession -ComputerName $linuxIp -Credential $rootCredential -Force -WarningAction SilentlyContinue -ErrorAction Stop
154 | }
155 | if (-not $sftpSession) {
156 | $sftpSession = New-SFTPSession -ComputerName $linuxIp -Credential $rootCredential -Force -WarningAction SilentlyContinue -ErrorAction Stop
157 | }
158 | break
159 | } catch {
160 | Write-PSFMessage -Level Host -Message "Still waiting ... [$($sshSession.Connected) / $($sftpSession.Connected)] ($_)"
161 | Start-Sleep -Seconds 60
162 | }
163 | }
164 |
165 |
166 | # As we do a lot of ssh commands via Invoke-SSHCommand from Posh-SSH lets use a wrapper to save some lines of code
167 | function Invoke-MySSHCommand {
168 | param(
169 | [SSH.SshSession]$SSHSession,
170 | [string[]]$Command,
171 | [int]$TimeOut = 9999,
172 | [switch]$ShowOutput
173 | )
174 | $returnValue = $true
175 | foreach ($cmd in $Command) {
176 | while ($true) {
177 | try {
178 | $sshResult = Invoke-SSHCommand -SSHSession $SSHSession -Command $cmd -EnsureConnection -TimeOut $TimeOut -ShowStandardOutputStream:$ShowOutput -ShowErrorOutputStream:$ShowOutput -ErrorAction Stop
179 | break
180 | } catch {
181 | Write-PSFMessage -Level Host -Message "Command '$cmd' failed with $_"
182 | Start-Sleep -Seconds 10
183 | }
184 | }
185 | if ($sshResult.ExitStatus -gt 0) {
186 | Write-PSFMessage -Level Warning -Message "Command '$cmd' returned with ExitStatus $($sshResult.ExitStatus)"
187 | $returnValue = $false
188 | break
189 | }
190 | }
191 | return $returnValue
192 | }
193 |
194 | # To stop the execution if an ssh command failed we use this command in a pipeline after Invoke-MySSHCommand
195 | function Stop-ExecutionOnFailure {
196 | param(
197 | [Parameter(ValueFromPipeline = $true)][boolean]$InputObject
198 | )
199 | if (-not $InputObject) {
200 | break
201 | }
202 | }
203 |
204 |
205 | Write-PSFMessage -Level Host -Message "Updating packages on Linux"
206 | $sshCommands = @(
207 | 'yum -y update'
208 | )
209 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
210 |
211 | Write-PSFMessage -Level Host -Message "Installing docker on Linux"
212 | $sshCommands = @(
213 | 'yum install -y yum-utils'
214 | 'yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo'
215 | 'yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
216 | 'systemctl enable --now docker'
217 | )
218 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
219 |
220 | Write-PSFMessage -Level Host -Message "Installing 7zip on Linux"
221 | $sshCommands = @(
222 | 'yum install -y epel-release'
223 | 'yum install -y p7zip'
224 | )
225 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
226 |
227 | Write-PSFMessage -Level Host -Message "Installing PowerShell modules"
228 | $sshCommands = @(
229 | "pwsh -c 'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted'"
230 | "pwsh -c 'Install-Module -Name PSFramework'"
231 | "pwsh -c 'Install-Module -Name Microsoft.PowerShell.ConsoleGuiTools'"
232 | )
233 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
234 |
235 | Write-PSFMessage -Level Host -Message "Copying and loading or pulling and saving docker images"
236 | foreach ($db in $DatabaseDefinition) {
237 | $imagePath = "$labSources\CustomAssets\DockerImages\$($db.ContainerName).tar.gz"
238 | if (Test-Path -Path $imagePath) {
239 | Write-PSFMessage -Level Host -Message "Found saved image for $($db.ContainerName), will copy and load image"
240 | Set-SFTPItem -SFTPSession $sftpSession -Destination /tmp -Path $imagePath -ErrorAction Stop
241 | $sshCommands = @(
242 | "docker load -i /tmp/$($db.ContainerName).tar.gz"
243 | "rm /tmp/$($db.ContainerName).tar.gz"
244 | )
245 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
246 | } else {
247 | Write-PSFMessage -Level Host -Message "No saved image found for $($db.ContainerName), will pull and save image"
248 | $sshCommands = @(
249 | "docker pull $($db.ContainerImage)"
250 | "docker save -o /tmp/$($db.ContainerName).tar $($db.ContainerImage)"
251 | "gzip /tmp/$($db.ContainerName).tar"
252 | )
253 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
254 | Get-SFTPItem -SFTPSession $sftpSession -Path "/tmp/$($db.ContainerName).tar.gz" -Destination "$labSources\CustomAssets\DockerImages" -ErrorAction Stop
255 | }
256 | }
257 |
258 | Write-PSFMessage -Level Host -Message "Starting databases on docker"
259 | foreach ($db in $DatabaseDefinition) {
260 | Write-PSFMessage -Level Host -Message "Starting docker container $($db.ContainerName)"
261 | $sshCommands = @(
262 | "docker run --name $($db.ContainerName) --memory=$($db.ContainerMemoryGB)g $($db.ContainerConfig) --detach --restart always $($db.ContainerImage)"
263 | )
264 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
265 | }
266 |
267 | Write-PSFMessage -Level Host -Message "Downloading PowerShell-for-DBAs"
268 | $sshCommands = @(
269 | 'curl -sL https://github.com/andreasjordan/PowerShell-for-DBAs/tarball/main | tar zx --transform "s,^[^/]+,PowerShell-for-DBAs,x"'
270 | )
271 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands | Stop-ExecutionOnFailure
272 |
273 | Write-PSFMessage -Level Host -Message "Copying database definition information to Linux"
274 | $DatabaseDefinition | ConvertTo-Json | Set-Content -Path "$labSources\tmp_DatabaseDefinition.json"
275 | Set-SFTPItem -SFTPSession $sftpSession -Destination /tmp -Path "$labSources\tmp_DatabaseDefinition.json" -ErrorAction Stop
276 | Remove-Item -Path "$labSources\tmp_DatabaseDefinition.json"
277 |
278 | Write-PSFMessage -Level Host -Message "Creating sample databases"
279 | $sshCommands = @(
280 | 'pwsh ./PowerShell-for-DBAs/PowerShell/01_SetupSampleDatabases.ps1'
281 | )
282 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands -ShowOutput | Stop-ExecutionOnFailure
283 |
284 | Write-PSFMessage -Level Host -Message "Creating sample schemas"
285 | $sshCommands = @(
286 | 'pwsh ./PowerShell-for-DBAs/PowerShell/02_SetupSampleSchemas.ps1'
287 | )
288 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands -ShowOutput | Stop-ExecutionOnFailure
289 |
290 | Write-PSFMessage -Level Host -Message "Importing sample data from JSON"
291 | $sshCommands = @(
292 | 'pwsh ./PowerShell-for-DBAs/PowerShell/03_ImportSampleDataFromJson.ps1'
293 | )
294 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands -ShowOutput | Stop-ExecutionOnFailure
295 |
296 | Write-PSFMessage -Level Host -Message "Importing sample data from Stackexchange"
297 | $sshCommands = @(
298 | 'pwsh ./PowerShell-for-DBAs/PowerShell/04_ImportSampleDataFromStackexchange.ps1'
299 | )
300 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands -ShowOutput | Stop-ExecutionOnFailure
301 |
302 | Write-PSFMessage -Level Host -Message "Importing sample geographic data"
303 | $sshCommands = @(
304 | 'pwsh ./PowerShell-for-DBAs/PowerShell/05_ImportSampleGeographicData.ps1'
305 | )
306 | Invoke-MySSHCommand -SSHSession $sshSession -Command $sshCommands -ShowOutput | Stop-ExecutionOnFailure
307 |
308 | Write-PSFMessage -Level Host -Message "finished"
309 |
310 | <#
311 |
312 | Logging:
313 |
314 | 11:11:23|00:00:00|00:00:00.000| Initialization
315 | 11:11:23|00:00:00|00:00:00.167| - Host operating system version: 'Microsoft Windows 10 Pro, 10.0.19045.0'
316 | 11:11:23|00:00:00|00:00:00.175| - Creating new lab definition with name 'DockerDatabases'
317 | 11:11:23|00:00:00|00:00:00.190| - Location of lab definition files will be 'C:\ProgramData\AutomatedLab/Labs/DockerDatabases'
318 | 11:11:24|00:00:00|00:00:00.817| - Location of LabSources folder is 'C:\AutomatedLab-Sources'
319 | 11:11:24|00:00:00|00:00:00.000| - Auto-adding ISO files
320 | 11:11:24|00:00:01|00:00:00.508| - Added 'C:\AutomatedLab-Sources\ISOs\2022_x64_EN_Eval.iso'
321 | 11:11:24|00:00:01|00:00:00.523| - Added 'C:\AutomatedLab-Sources\ISOs\CentOS-7-x86_64-DVD-2207-02.iso'
322 | 11:11:25|00:00:01|00:00:00.532| - Done
323 | 11:11:25|00:00:01|00:00:00.555| - Path for VMs specified as 'C:\AutomatedLab-VMs'
324 | 11:11:25|00:00:01|00:00:00.000| - Adding Hyper-V machine definition 'DockerDatabases'
325 | 11:11:27|00:00:04|00:00:02.080| - Done
326 | 11:11:28|00:00:04|00:00:02.556| Estimated (additional) local drive space needed for all machines: 2 GB
327 | 11:11:28|00:00:04|00:00:02.600| Location of Hyper-V machines will be 'C:\AutomatedLab-VMs'
328 | 11:11:28|00:00:04|00:00:02.901| Done
329 | 11:11:29|00:00:05|00:00:03.644| Lab 'DockerDatabases' hosted on 'HyperV' imported with 1 machines
330 | 11:11:29|00:00:06|00:00:00.000| Creating virtual networks
331 | 11:11:29|00:00:06|00:00:00.000| - Creating Hyper-V virtual network 'DockerDatabases'
332 | 11:11:33|00:00:10|00:00:04.061| - Done
333 | 11:11:34|00:00:10|00:00:04.157| - Done
334 | 11:11:34|00:00:10|00:00:04.232| done
335 | 11:11:34|00:00:10|00:00:00.000| Creating Additional Disks
336 | 11:11:34|00:00:10|00:00:00.142| - Done
337 | 11:11:34|00:00:10|00:00:00.000| Creating VMs
338 | 11:11:34|00:00:10|00:00:00.187| - The hosts file has been added 1 records. Clean them up using 'Remove-Lab' or manually if needed
339 | 11:11:34|00:00:10|00:00:00.000| - Waiting for all machines to finish installing
340 | 11:11:34|00:00:10|00:00:00.000| - Creating HyperV machine 'DockerDatabases'....
341 | 11:11:45|00:00:21|00:00:10.469| - Done
342 | 11:11:45|00:00:21|00:00:10.475| - Done
343 | 11:11:45|00:00:21|00:00:10.777| - Done
344 | 11:11:45|00:00:22|00:00:00.000| Starting remaining machines
345 | 11:11:45|00:00:22|00:00:00.022| - Waiting for machines to start up...Done
346 | 11:11:46|00:00:22|00:00:00.000| Installing RDS certificates of lab machines
347 | 11:11:46|00:00:22|00:00:00.034| - Done
348 | 11:11:48|00:00:25|00:00:02.688| .
349 | [11:11:48][Docker_Databases.ps1] Waiting until we can connect and open ssh and sftp session
350 | [11:11:58][Docker_Databases.ps1] Still waiting ... [ / ] (Connection failed to establish within 10000 milliseconds.)
351 | [11:13:09][Docker_Databases.ps1] Still waiting ... [ / ] (Connection failed to establish within 10000 milliseconds.)
352 | [11:14:11][Docker_Databases.ps1] Still waiting ... [ / ] (Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte)
353 | [11:15:13][Docker_Databases.ps1] Still waiting ... [ / ] (Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte)
354 | [11:16:15][Docker_Databases.ps1] Still waiting ... [ / ] (Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte)
355 | [11:17:15][Docker_Databases.ps1] Updating packages on Linux
356 | [11:18:28][Docker_Databases.ps1] Installing docker on Linux
357 | [11:19:00][Docker_Databases.ps1] Installing 7zip on Linux
358 | [11:19:10][Docker_Databases.ps1] Installing PowerShell modules
359 | [11:19:30][Docker_Databases.ps1] Copying and loading or pulling and saving docker images
360 | [11:19:30][Docker_Databases.ps1] Found saved image for SQLServer, will copy and load image
361 | [11:20:05][Docker_Databases.ps1] Found saved image for Oracle, will copy and load image
362 | [11:23:54][Docker_Databases.ps1] Found saved image for MySQL, will copy and load image
363 | [11:24:06][Docker_Databases.ps1] Found saved image for MariaDB, will copy and load image
364 | [11:24:16][Docker_Databases.ps1] Found saved image for PostgreSQL, will copy and load image
365 | [11:24:26][Docker_Databases.ps1] Found saved image for PostGIS, will copy and load image
366 | [11:24:37][Docker_Databases.ps1] Starting databases on docker
367 | [11:24:37][Docker_Databases.ps1] Starting docker container SQLServer
368 | [11:24:38][Docker_Databases.ps1] Starting docker container Oracle
369 | [11:24:39][Docker_Databases.ps1] Starting docker container MySQL
370 | [11:24:39][Docker_Databases.ps1] Starting docker container MariaDB
371 | [11:24:39][Docker_Databases.ps1] Starting docker container PostgreSQL
372 | [11:24:40][Docker_Databases.ps1] Starting docker container PostGIS
373 | [11:24:41][Docker_Databases.ps1] Downloading PowerShell-for-DBAs
374 | [11:24:42][Docker_Databases.ps1] Copying database definition information to Linux
375 | [11:24:42][Docker_Databases.ps1] Creating sample databases
376 | [09:24:47][01_SetupSampleDatabases.ps1] Waiting for connection to SQL Server
377 | [09:25:18][01_SetupSampleDatabases.ps1] Creating sample database and user on SQL Server finished
378 | [09:25:20][01_SetupSampleDatabases.ps1] Waiting for connection to Oracle
379 | [09:25:20][01_SetupSampleDatabases.ps1] Creating sample users on Oracle finished
380 | [09:25:27][01_SetupSampleDatabases.ps1] Waiting for connection to MySQL
381 | [09:25:27][01_SetupSampleDatabases.ps1] Creating sample database and user on MySQL finished
382 | [09:25:27][01_SetupSampleDatabases.ps1] Waiting for connection to MariaDB
383 | [09:25:27][01_SetupSampleDatabases.ps1] Creating sample database and user on MariaDB finished
384 | [09:25:31][01_SetupSampleDatabases.ps1] Waiting for connection to PostgreSQL
385 | [09:25:31][01_SetupSampleDatabases.ps1] Creating sample database and user on PostgreSQL finished
386 | [09:25:31][01_SetupSampleDatabases.ps1] Waiting for connection to PostGIS
387 | [09:25:31][01_SetupSampleDatabases.ps1] Creating sample database and user on PostGIS finished
388 | [11:25:31][Docker_Databases.ps1] Creating sample schemas
389 | [09:25:33][02_SetupSampleSchemas.ps1] Creating sample schema on SQL Server finished
390 | [09:25:34][02_SetupSampleSchemas.ps1] Creating sample schema on Oracle finished
391 | [09:25:34][02_SetupSampleSchemas.ps1] Creating sample schema on MySQL finished
392 | [09:25:35][02_SetupSampleSchemas.ps1] Creating sample schema on MariaDB finished
393 | [09:25:35][02_SetupSampleSchemas.ps1] Creating sample schema on PostgreSQL finished
394 | [09:25:35][02_SetupSampleSchemas.ps1] Creating sample schema on PostGIS finished
395 | [11:25:34][Docker_Databases.ps1] Importing sample data from JSON
396 | [09:25:40][03_ImportSampleDataFromJson.ps1] Importing sample data to SQL Server finished in 3.0137833 seconds
397 | [09:25:46][03_ImportSampleDataFromJson.ps1] Importing sample data to Oracle finished in 5.2271296 seconds
398 | [09:25:48][03_ImportSampleDataFromJson.ps1] Importing sample data to MySQL finished in 2.2646515 seconds
399 | [09:25:50][03_ImportSampleDataFromJson.ps1] Importing sample data to MariaDB finished in 1.801707 seconds
400 | [09:26:14][03_ImportSampleDataFromJson.ps1] Importing sample data to PostgreSQL finished in 23.9747867 seconds
401 | [11:26:13][Docker_Databases.ps1] Importing sample data from Stackexchange
402 | [09:26:20][04_ImportSampleDataFromStackexchange.ps1] Dowload sample data finished in 4.0068473 seconds
403 | [09:26:25][04_ImportSampleDataFromStackexchange.ps1] Importing sample data to SQL Server finished in 5.9283759 seconds
404 | [09:26:34][04_ImportSampleDataFromStackexchange.ps1] Importing sample data to Oracle finished in 8.6976567 seconds
405 | [09:26:41][04_ImportSampleDataFromStackexchange.ps1] Importing sample data to MySQL finished in 6.5348029 seconds
406 | [09:26:46][04_ImportSampleDataFromStackexchange.ps1] Importing sample data to MariaDB finished in 5.244922 seconds
407 | [09:27:40][04_ImportSampleDataFromStackexchange.ps1] Importing sample data to PostgreSQL finished in 54.2771984 seconds
408 | [11:27:40][Docker_Databases.ps1] Importing sample geographic data
409 | [09:27:52][05_ImportSampleGeographicData.ps1] Importing sample geographic data to Oracle finished in 4.8504032 seconds
410 | [09:27:54][05_ImportSampleGeographicData.ps1] Importing sample geographic data to PostGIS finished in 2.638402 seconds
411 | [11:27:54][Docker_Databases.ps1] finished
412 |
413 | #>
--------------------------------------------------------------------------------
/AutomatedLab/CustomScripts/README.md:
--------------------------------------------------------------------------------
1 | Here are the scripts for building different labs.
2 |
3 | All scripts must be executed in an administrative PowerShell.
4 |
5 | Most scripts need additional components from the CustomAssets folder.
6 |
7 |
8 |
9 | ## Docker_Databases
10 |
11 | This lab consists of a Linux VM with CentOS-7 and docker, as well as containers with various database systems.
12 |
13 | Currently possible:
14 | * Microsoft SQL Server
15 | * Oracle
16 | * MySQL
17 | * MariaDB
18 | * PostgreSQL
19 | * PostgreSQL with PostGIS
20 |
21 | A test user is also created in each of the database systems and test data is loaded. Code from my GitHub repository https://github.com/andreasjordan/PowerShell-for-DBAs is used for this.
22 |
23 | Currently possible:
24 | * StackOverflow's database with the same schema and sample data for all database systems. All information comes from the mentioned GitHub repository.
25 | * Polygons of country boundaries. The data is downloaded directly from https://datahub.io/core/geo-countries.
26 |
27 | The script is currently "in progress". Additional wishes and comments are welcome.
28 |
29 |
30 | ## SQLServer_AlwaysOn
31 |
32 | This lab is specifically intended for running the SQL Server Always On Availability Groups demos, which are stored here: https://github.com/andreasjordan/demos/tree/master/AlwaysOn
33 |
34 | Therefore, the configuration of the lab may only be adjusted if the demo scripts are adjusted at the same time.
35 |
36 | The lab consists of five Windows Server 2022 machines, one of which is a domain controller, one server on which the demo scripts are executed and three servers with SQL Server instances.
37 |
38 |
39 | ## Win2022_Oracle
40 |
41 | The lab consists of three Windows Server 2022 machines. The Oracle client is installed on one server, and an Oracle server on each of the other two.
42 |
43 |
44 | ## Win2022_SQLServer
45 |
46 | The lab consists of a Windows Server 2022 machine with an instance of SQL Server 2019 Express installed.
47 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomScripts/Win2022_Oracle.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Continue'
2 |
3 | Import-Module -Name AutomatedLab
4 |
5 | $LabName = "WinOra"
6 |
7 |
8 | <# Some commands that I use for importing, removing, stopping, starting or connecting to the lab:
9 |
10 | Import-Lab -Name $LabName -NoValidation
11 |
12 | Remove-Lab -Name $LabName -Confirm:$false; Get-NetNat -Name $LabName -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
13 |
14 | Stop-VM -Name $LabName-*
15 | Start-VM -Name $LabName-*
16 |
17 | vmconnect.exe localhost $LabName-WIN-CL
18 |
19 | #>
20 |
21 |
22 | $LabNetworkBase = '192.168.111'
23 | $LabDnsServer = '1.1.1.1'
24 |
25 | $LabAdminUser = 'User'
26 | $LabAdminPassword = 'Passw0rd!'
27 |
28 | $MachineDefinition = @(
29 | @{
30 | Name = 'WIN-CL'
31 | ResourceName = "$LabName-WIN-CL"
32 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
33 | Memory = 4GB
34 | Processors = 4
35 | Network = $LabName
36 | IpAddress = "$LabNetworkBase.10"
37 | Gateway = "$LabNetworkBase.1"
38 | DnsServer1 = $LabDnsServer
39 | TimeZone = 'W. Europe Standard Time'
40 | }
41 | @{
42 | Name = 'WIN-DB01'
43 | ResourceName = "$LabName-WIN-DB01"
44 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
45 | Memory = 4GB
46 | Processors = 4
47 | Network = $LabName
48 | IpAddress = "$LabNetworkBase.11"
49 | Gateway = "$LabNetworkBase.1"
50 | DnsServer1 = $LabDnsServer
51 | TimeZone = 'W. Europe Standard Time'
52 | }
53 | @{
54 | Name = 'WIN-DB02'
55 | ResourceName = "$LabName-WIN-DB02"
56 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
57 | Memory = 4GB
58 | Processors = 4
59 | Network = $LabName
60 | IpAddress = "$LabNetworkBase.12"
61 | Gateway = "$LabNetworkBase.1"
62 | DnsServer1 = $LabDnsServer
63 | TimeZone = 'W. Europe Standard Time'
64 | }
65 | )
66 |
67 | $ChocolateyPackages = @(
68 | 'notepadplusplus'
69 | '7zip'
70 | )
71 |
72 | $CopySoftware = @(
73 | "$labSources\CustomAssets\Software\OracleXE213_Win64.zip" # Oracle Express for Windows from: https://www.oracle.com/database/technologies/xe-downloads.html
74 | "$labSources\CustomAssets\Software\WINDOWS.X64_193000_client.zip" # Oracle Client 19c from: https://www.oracle.com/database/technologies/oracle19c-windows-downloads.html
75 | )
76 |
77 |
78 |
79 | New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV -VmPath $LabVmPath
80 | Set-LabInstallationCredential -Username $LabAdminUser -Password $LabAdminPassword
81 | Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace "$LabNetworkBase.0/24"
82 | foreach ($md in $MachineDefinition) {
83 | Add-LabMachineDefinition @md
84 | }
85 | Install-Lab -NoValidation
86 |
87 | # I use NetNat to provide internat to the virtual maschines
88 | $null = New-NetNat -Name $LabName -InternalIPInterfaceAddressPrefix "$LabNetworkBase.0/24"
89 |
90 |
91 | $allVMs = @( )
92 | $clientVMs = @( )
93 | $databaseVMs = @( )
94 | $allVMs += Get-LabVM
95 | $clientVMs += $allVMs | Where-Object Name -like "*-CL*"
96 | $databaseVMs += $allVMs | Where-Object Name -like "*-DB*"
97 |
98 |
99 | Invoke-LabCommand -ComputerName $allVMs.Name -ActivityName 'Disable Windows updates' -ScriptBlock {
100 | # https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings
101 | Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1
102 | }
103 |
104 | Invoke-LabCommand -ComputerName $allVMs.Name -ActivityName 'Setting my favorite explorer settings' -ScriptBlock {
105 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name HideFileExt -Value 0
106 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneShowAllFolders -Value 1
107 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneExpandToCurrentFolder -Value 1
108 | }
109 |
110 | if ($HostEntries.Count -gt 0) {
111 | Invoke-LabCommand -ComputerName $allVMs.Name -ActivityName 'SetupHostEntries' -ArgumentList @(, $HostEntries) -ScriptBlock {
112 | param($HostEntries)
113 |
114 | $HostEntries | Add-Content -Path C:\Windows\System32\drivers\etc\hosts
115 | }
116 | }
117 |
118 |
119 | foreach ($file in $CopySoftware) {
120 | Copy-LabFileItem -Path $file -ComputerName $allVMs.Name -DestinationFolderPath C:\Software
121 | }
122 |
123 |
124 | Invoke-LabCommand -ComputerName $databaseVMs.Name -ActivityName 'Installing Oracle Server' -ArgumentList $LabAdminPassword -ScriptBlock {
125 | param($Password)
126 | $rspContent = @(
127 | 'INSTALLDIR=C:\oracle\product\21c\'
128 | "PASSWORD=$Password"
129 | 'LISTENER_PORT=1521'
130 | 'EMEXPRESS_PORT=5550'
131 | 'CHAR_SET=AL32UTF8'
132 | 'DB_DOMAIN='
133 | )
134 | $argumentList = @(
135 | '/s'
136 | '/v"RSP_FILE=C:\Software\OracleServerInstall.rsp"'
137 | '/v"/L*v C:\Software\OracleServerSetup.log"'
138 | '/v"/qn"'
139 | )
140 | Expand-Archive -Path C:\Software\OracleXE213_Win64.zip -DestinationPath C:\Software\OracleXE213_Win64
141 | $rspContent | Set-Content -Path C:\Software\OracleServerInstall.rsp
142 | Start-Process -FilePath C:\Software\OracleXE213_Win64\setup.exe -ArgumentList $argumentList -WorkingDirectory C:\Software -NoNewWindow -Wait
143 | }
144 |
145 | $numberOfOracleServices = Invoke-LabCommand -ComputerName $databaseVMs.Name -ActivityName 'Testing Oracle Server' -PassThru -ScriptBlock {
146 | (Get-Service | Where-Object Name -like Oracle*).Count
147 | }
148 |
149 | if ($numberOfOracleServices -lt 5) {
150 | Write-Warning -Message "We only have $numberOfOracleServices oracle services - so installation failed. Please rebuild the lab, will start removing the lab now..."
151 | Remove-Lab -Name $LabName -Confirm:$false
152 | Get-NetNat -Name $LabName -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
153 | break
154 | }
155 |
156 |
157 |
158 | <#
159 |
160 | #------------------------------------------------------------------------------
161 | #Name : INSTALL_TYPE
162 | #Datatype : String
163 | #Description: Installation type of the component.
164 | #
165 | # The following choices are available. The value should contain
166 | # only one of these choices.
167 | # - Administrator
168 | # - Runtime
169 | # - InstantClient
170 | # - Custom
171 | #
172 | #Example : INSTALL_TYPE = Administrator
173 | #------------------------------------------------------------------------------
174 |
175 | #-------------------------------------------------------------------------------
176 | # Name : oracle.install.client.customComponents
177 | # Datatype : StringList
178 | #
179 | # This property is considered only if INSTALL_TYPE is set to "Custom"
180 | #
181 | # Description: List of Client Components you would like to install
182 | #
183 | # The following choices are available. You may specify any
184 | # combination of these choices. The components you choose should
185 | # be specified in the form "internal-component-name:version"
186 | # Below is a list of components you may specify to install.
187 | #
188 | # oracle.sqlj:19.0.0.0.0 -- "Oracle SQLJ"
189 | # oracle.rdbms.util:19.0.0.0.0 -- "Oracle Database Utilities"
190 | # oracle.javavm.client:19.0.0.0.0 -- "Oracle Java Client"
191 | # oracle.sqlplus:19.0.0.0.0 -- "SQL*Plus"
192 | # oracle.dbjava.jdbc:19.0.0.0.0 -- "Oracle JDBC/THIN Interfaces"
193 | # oracle.ldap.client:19.0.0.0.0 -- "Oracle Internet Directory Client"
194 | # oracle.rdbms.oci:19.0.0.0.0 -- "Oracle Call Interface (OCI)"
195 | # oracle.precomp:19.0.0.0.0 -- "Oracle Programmer"
196 | # oracle.xdk:19.0.0.0.0 -- "Oracle XML Development Kit"
197 | # oracle.network.aso:19.0.0.0.0 -- "Oracle Advanced Security"
198 | # oracle.oraolap.mgmt:19.0.0.0.0 -- "OLAP Analytic Workspace Manager and Worksheet"
199 | # oracle.network.client:19.0.0.0.0 -- "Oracle Net"
200 | # oracle.network.cman:19.0.0.0.0 -- "Oracle Connection Manager"
201 | # oracle.network.listener:19.0.0.0.0 -- "Oracle Net Listener"
202 | # oracle.ordim.client:19.0.0.0.0 -- "Oracle Multimedia Client Option"
203 | # oracle.odbc:19.0.0.0.0 -- "Oracle ODBC Driver"
204 | # oracle.has.client:19.0.0.0.0 -- "Oracle Clusterware High Availability API"
205 | # oracle.dbdev:19.0.0.0.0 -- "Oracle SQL Developer"
206 | # oracle.rdbms.scheduler:19.0.0.0.0 -- "Oracle Scheduler Agent"
207 | # oracle.ntoramts:19.0.0.0.0 -- "Oracle Services For Microsoft Transaction Server"
208 | # oracle.ntoledb:19.0.0.0.0 -- "Oracle Provider for OLE DB"
209 | # oracle.ntoledb.odp_net_2:19.0.0.0.0 -- "Oracle Data Provider for .NET"
210 | # oracle.aspnet_2:19.0.0.0.0 -- "Oracle Providers for ASP.NET"
211 | #
212 | # Example : oracle.install.client.customComponents="oracle.precomp:19.0.0.0.0","oracle.oraolap.mgmt:19.0.0.0.0","oracle.rdbms.scheduler:19.0.0.0.0"
213 | #-------------------------------------------------------------------------------
214 |
215 |
216 | #>
217 |
218 | Invoke-LabCommand -ComputerName $clientVMs.Name -ActivityName 'Installing Oracle Client' -ScriptBlock {
219 | $rspContent = @(
220 | 'ORACLE_BASE=C:\oracle'
221 | 'ORACLE_HOME=C:\oracle\product\19.0.0\client_1'
222 | 'oracle.install.responseFileVersion=/oracle/install/rspfmt_clientinstall_response_schema_v19.0.0'
223 | 'oracle.install.IsBuiltInAccount=true'
224 | 'oracle.install.client.installType=Custom'
225 | 'oracle.install.client.customComponents=oracle.ntoledb.odp_net_2:19.0.0.0.0,oracle.sqlplus:19.0.0.0.0,oracle.rdbms.util:19.0.0.0.0'
226 | )
227 | $argumentList = @(
228 | '-silent'
229 | '-responseFile C:\Software\OracleClientInstall.rsp'
230 | '-noConsole'
231 | )
232 | Expand-Archive -Path C:\Software\WINDOWS.X64_193000_client.zip -DestinationPath C:\Software\WINDOWS.X64_193000_client
233 | $rspContent | Set-Content -Path C:\Software\OracleClientInstall.rsp
234 | Start-Process -FilePath C:\Software\WINDOWS.X64_193000_client\client\setup.exe -ArgumentList $argumentList -Wait
235 | }
236 |
237 |
238 | if ($ChocolateyPackages.Count -gt 0) {
239 | Invoke-LabCommand -ComputerName $allVMs.Name -ActivityName 'Installing chocolatey packages' -ArgumentList @(, $ChocolateyPackages) -ScriptBlock {
240 | param($ChocolateyPackages)
241 |
242 | $ErrorActionPreference = 'Stop'
243 |
244 | $logPath = 'C:\DeployDebug\InstallChocolateyPackages.log'
245 |
246 | try {
247 | Invoke-Expression -Command ([System.Net.WebClient]::new().DownloadString('https://chocolatey.org/install.ps1')) *>$logPath
248 | $installResult = choco install $ChocolateyPackages --confirm --limitoutput --no-progress *>&1
249 | if ($installResult -match 'Warnings:') {
250 | Write-Warning -Message 'Chocolatey generated warnings'
251 | }
252 | $info = $installResult -match 'Chocolatey installed (\d+)/(\d+) packages' | Select-Object -First 1
253 | if ($info -match 'Chocolatey installed (\d+)/(\d+) packages') {
254 | if ($Matches[1] -ne $Matches[2]) {
255 | Write-Warning -Message "Chocolatey only installed $($Matches[1]) of $($Matches[2]) packages"
256 | $installResult | Add-Content -Path $logPath
257 | }
258 | } else {
259 | Write-Warning -Message "InstallResult: $installResult"
260 | }
261 | } catch {
262 | $message = "Setting up Chocolatey failed: $_"
263 | $message | Add-Content -Path $logPath
264 | Write-Warning -Message $message
265 | }
266 | }
267 | }
268 |
269 |
270 | if ($PowerShellModules.Count -gt 0) {
271 | Invoke-LabCommand -ComputerName $allVMs.Name -ActivityName 'Installing PowerShell modules' -ArgumentList @(, $PowerShellModules) -ScriptBlock {
272 | param($PowerShellModules)
273 |
274 | $logPath = 'C:\DeployDebug\InstallPowerShellModules.log'
275 |
276 | $ErrorActionPreference = 'Stop'
277 |
278 | try {
279 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
280 | if ((Get-PackageProvider -ListAvailable).Name -notcontains 'Nuget') {
281 | $null = Install-PackageProvider -Name Nuget -Force
282 | 'Install-PackageProvider ok' | Add-Content -Path $logPath
283 | } else {
284 | 'Install-PackageProvider not needed' | Add-Content -Path $logPath
285 | }
286 | if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
287 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
288 | 'Set-PSRepository ok' | Add-Content -Path $logPath
289 | } else {
290 | 'Set-PSRepository not needed' | Add-Content -Path $logPath
291 | }
292 | foreach ($name in $PowerShellModules) {
293 | if (-not (Get-Module -Name $name -ListAvailable)) {
294 | Install-Module -Name $name
295 | "Install-Module $name ok" | Add-Content -Path $logPath
296 | } else {
297 | "Install-Module $name not needed" | Add-Content -Path $logPath
298 | }
299 | }
300 | } catch {
301 | $message = "Setting up PowerShell failed: $_"
302 | $message | Add-Content -Path $logPath
303 | Write-Warning -Message $message
304 | }
305 | }
306 | }
307 |
308 |
--------------------------------------------------------------------------------
/AutomatedLab/CustomScripts/Win2022_SQLServer.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = 'Continue'
2 |
3 | Import-Module -Name AutomatedLab
4 |
5 | $LabName = "WinSql"
6 | $LabNetworkBase = '192.168.113'
7 | $LabDnsServer = '1.1.1.1'
8 |
9 | $LabAdminUser = 'User'
10 | $LabAdminPassword = 'Passw0rd!'
11 |
12 | $MachineDefinition = @{
13 | Name = 'SQLServer'
14 | OperatingSystem = 'Windows Server 2022 Standard Evaluation (Desktop Experience)'
15 | Memory = 4GB
16 | Processors = 4
17 | Network = $LabName
18 | IpAddress = "$LabNetworkBase.10"
19 | Gateway = "$LabNetworkBase.1"
20 | DnsServer1 = $LabDnsServer
21 | TimeZone = 'W. Europe Standard Time'
22 | }
23 |
24 | $CopySoftware = @(
25 | "$labSources\CustomAssets\Software\SQLEXPR_x64_ENU.exe" # SQL Server Express from: https://www.microsoft.com/en-US/download/details.aspx?id=101064
26 | "$labSources\CustomAssets\Software\SQLServer2019-KB5017593-x64.exe" # SQL Server 2019 CU 18
27 | )
28 |
29 |
30 | <# Some commands that I use for importing, removing, stopping, starting or connecting to the lab:
31 |
32 | Import-Lab -Name $LabName -NoValidation
33 |
34 | Remove-Lab -Name $LabName -Confirm:$false; Get-NetNat -Name $LabName -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false
35 |
36 | Stop-VM -Name $MachineDefinition.Name
37 | Start-VM -Name $MachineDefinition.Name
38 |
39 | #>
40 |
41 |
42 | ### End of configuration ###
43 |
44 |
45 | New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV
46 | Set-LabInstallationCredential -Username $LabAdminUser -Password $LabAdminPassword
47 | Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace "$LabNetworkBase.0/24"
48 | Add-LabMachineDefinition @MachineDefinition
49 | Install-Lab -NoValidation
50 |
51 | $null = New-NetNat -Name $LabName -InternalIPInterfaceAddressPrefix "$LabNetworkBase.0/24"
52 |
53 | Invoke-LabCommand -ComputerName $MachineDefinition.Name -ActivityName 'Disable Windows updates' -ScriptBlock {
54 | # https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings
55 | Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1
56 | }
57 |
58 | Invoke-LabCommand -ComputerName $MachineDefinition.Name -ActivityName 'Setting my favorite explorer settings' -ScriptBlock {
59 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name HideFileExt -Value 0
60 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneShowAllFolders -Value 1
61 | Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneExpandToCurrentFolder -Value 1
62 | }
63 |
64 | foreach ($file in $CopySoftware) {
65 | Copy-LabFileItem -Path $file -ComputerName $MachineDefinition.Name -DestinationFolderPath C:\Software
66 | }
67 |
68 | Invoke-LabCommand -ComputerName $MachineDefinition.Name -ActivityName 'Installing SQL Server' -ArgumentList $LabAdminPassword -ScriptBlock {
69 | param($Password)
70 | $argumentList = @(
71 | '/x:C:\Software\SQLEXPR_x64_ENU'
72 | '/q'
73 | '/IACCEPTSQLSERVERLICENSETERMS'
74 | '/ACTION=INSTALL'
75 | '/UpdateEnabled=True'
76 | '/UpdateSource=C:\Software'
77 | '/FEATURES=SQL'
78 | '/INSTANCENAME=SQLEXPRESS'
79 | '/SECURITYMODE=SQL'
80 | "/SAPWD=$Password"
81 | '/TCPENABLED=1'
82 | '/SQLSVCINSTANTFILEINIT=True'
83 | )
84 | Start-Process -FilePath C:\Software\SQLEXPR_x64_ENU.exe -ArgumentList $argumentList -Wait
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/AutomatedLab/README.md:
--------------------------------------------------------------------------------
1 | # AutomatedLab
2 |
3 | Information and resources for using the PowerShell module AutomatedLab (https://automatedlab.org/) to set up labs.
4 |
5 | This is a modified copy of a private repository, so some things might not work without further modification. All information and scripts are intended as inspiration for own developments.
6 |
7 |
8 | ## Installation
9 |
10 | All commands must be executed in an administrative PowerShell.
11 |
12 | For installation, the commands from the scripts in this directory can simply be copied into a PowerShell ISE and executed there line by line or block by block.
13 |
14 |
15 | ### 01_Install_HyperV.ps1
16 |
17 | In advance, the ExecutionPolicy is tested here and set to RemoteSigned if necessary.
18 |
19 | The Windows Hyper-V function is then installed and the computer is restarted. If Hyper-V is already in use, this step can be skipped.
20 |
21 |
22 | ### 02_Install_AutomatedLab.ps1
23 |
24 | First, PSGallery is set up as a trusted source for PowerShell packages. Since Microsoft servers only support the current TLS 1.2 protocol, the commented out line may still need to be used on older operating systems. On current operating systems, however, this should no longer be necessary. If errors occur, this may be due to the temporary inaccessibility of the PSGallery. Then some time later a new attempt has to be started.
25 |
26 | In addition to the PowerShell module AutomatedLab, Posh-SSH is also installed to enable convenient access to the Linux machines via SSH. The additional options during the installation of AutomatedLab are necessary, details are described [here](https://automatedlab.org/en/latest/Wiki/Basic/install/).
27 |
28 | The environment variable AUTOMATEDLAB_TELEMETRY_OPTIN controls whether a corresponding information should be sent to the developers when setting up and dismantling labs. We want to switch this off in our case.
29 |
30 | Deviating from the standard configuration, two configurations are changed to adjust the paths used. These are my recommendations, but anyone can deviate from them.
31 |
32 | The configuration "LabSourcesLocation" is later always accessible under the global variable `$labSources` and is also referenced in this way in all scripts. In this path above all the ISOs of the used operating systems are stored, which are needed only during the construction of the labs. Additional components and examples are also stored here. Also the ISOs of the SQL Servers and installation media of Oracle are stored later under this path. The default is only "LabSources" and offers thus first no reference to the used program, therefore the adjustment.
33 |
34 | The "VmPath" configuration controls the location of the created Hyper-V machines. Without this configuration, the fastest drive is automatically used and the directory is created there.
35 |
36 | After setting the configuration, the module is loaded and initialized. During this process, additional files are loaded from the internet.
37 |
38 | Since the module itself does not provide any operating system ISOs, you have to obtain them yourself. I recommend using the ISOs provided by Microsoft with evaluation versions of the operating systems here. The client operating systems Windows 10 and 11 have a test period of 90 days, the server operating system Windows Server 2022 has a test period of 180 days. In addition, the deployment of Windows Server 2022 is a lot faster than the deployment of Windows 10 or 11, which is why I only use Windows Server 2022 in my labs myself. The Linux variants specified here can be installed automatically, newer versions are not currently supported. I mainly use CentOS-7, because the installation is finished faster than with CentOS-Stream-9 or openSUSE-Leap-15.2.
39 |
40 |
41 | ### 03_Setup_TestLab.ps1
42 |
43 | At this point you can build a first lab to test the functionality.
44 |
45 | The lab can be configured in a few places: Firstly, the creation of the domain can be switched off to shorten the installation time. Only the two machines "Windows" and "Linux" are then set up. On the other hand the used DNS server can be fixed, because the automatic determination could not be tested sufficiently. However, the DNS server setting is only used in the configuration without domain, because with domain the DC takes over the role of the DNS proxy. In addition, the Chocolatey packages, PowerShell modules and Docker containers installed for testing can be customized.
46 |
47 |
48 | ### 04_Download_CustomAssets.ps1
49 |
50 | The script downloads this repository and moves the "CustomAssets" and "CustomScripts" directories to the "$labSources" directory. Directly contained there are currently only the backups of GPOs that I regularly use in my labs. There are also subdirectories for other files, but I don't want to include these files in the repository here. I only provide a list of files that I use internally.
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Andreas Jordan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This is my demo space
2 |
3 | This is mainly for demos with Microsoft SQL Server and PowerShell. And very much work in progress all the time.
4 |
5 |
6 | ## Setup a lab environment for demos
7 |
8 | I switched from PSAutoLab to AutomatedLab end of 2022, so you now find my scripts and resources to setup my lab in the AutomatedLab folder. Keep in mind that this is a modified copy of a private repository, so some things might not work without further modification. All information and scripts are intended as inspiration for own developments.
9 |
10 |
11 | ## Setup Always On with dbatools
12 |
13 | In the folder AlwaysOn you find scripts to setup a cluster, default instances and Always On Availability Groups with the PowerShell module [dbatools](https://dbatools.io/).
14 | In the folder AlwaysOn_MultiInstance the demo is extended to use named instances with various SQL Server versions.
15 |
16 |
17 | ## Output results of sp_Blitz in Markdown format
18 |
19 | In the folder sp_Blitz_markdown you find a script that converts the results of sp_Blitz in a bulleted list in Markdown format.
20 |
21 |
--------------------------------------------------------------------------------
/SQLServer/Fragmentation.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Part 1:
3 |
4 | Overall view of all pages related to user databases.
5 |
6 | Can be be executed from any database context
7 | and shows information about every user database,
8 | but object names cannot be retrieved.
9 | */
10 |
11 |
12 | /*
13 | Show one line per database.
14 | */
15 | SELECT database_id
16 | , DB_NAME(database_id) AS database_name
17 | , COUNT(*) AS pages
18 | , COUNT(*)*8/1024 AS mb_total
19 | , SUM(CAST(free_space_in_bytes AS bigint))/1024/1024 AS mb_free
20 | FROM sys.dm_os_buffer_descriptors
21 | WHERE database_id BETWEEN 5 and 32760
22 | GROUP BY database_id
23 | ORDER BY SUM(CAST(free_space_in_bytes AS bigint));
24 |
25 |
26 | /*
27 | Show one line per database, (relevant) page type and page level.
28 | */
29 | SELECT database_id
30 | , DB_NAME(database_id) AS database_name
31 | , page_type
32 | , page_level
33 | , COUNT(*) AS pages
34 | , COUNT(*)*8/1024 AS mb_total
35 | , SUM(CAST(free_space_in_bytes AS bigint))/1024/1024 AS mb_free
36 | FROM sys.dm_os_buffer_descriptors
37 | WHERE database_id BETWEEN 5 and 32760
38 | GROUP BY database_id
39 | , page_type
40 | , page_level
41 | -- comment out to see all page types:
42 | HAVING page_type NOT IN ('BOOT_PAGE', 'DIFF_MAP_PAGE', 'FILEHEADER_PAGE', 'GAM_PAGE', 'SGAM_PAGE', 'PFS_PAGE')
43 | ORDER BY database_id
44 | , page_type
45 | , page_level;
46 |
47 |
48 | /*
49 | Show one line per database and custom page type group.
50 | */
51 | WITH data AS (
52 | SELECT database_id
53 | , DB_NAME(database_id) AS database_name
54 | , CASE
55 | WHEN page_type = 'DATA_PAGE'
56 | THEN 'DATA_PAGE'
57 | WHEN page_type = 'INDEX_PAGE'
58 | AND page_level = 0
59 | THEN 'INDEX_PAGE_leaf'
60 | WHEN page_type = 'INDEX_PAGE'
61 | AND page_level > 0
62 | THEN 'INDEX_PAGE_non_leaf'
63 | ELSE 'other_page'
64 | END AS page_type_and_level
65 | , CASE
66 | WHEN page_type = 'DATA_PAGE'
67 | THEN 1
68 | WHEN page_type = 'INDEX_PAGE'
69 | AND page_level = 0
70 | THEN 2
71 | WHEN page_type = 'INDEX_PAGE'
72 | AND page_level > 0
73 | THEN 3
74 | ELSE 4
75 | END AS order_id
76 | , CAST(free_space_in_bytes AS bigint) AS free_space_in_bytes
77 | FROM sys.dm_os_buffer_descriptors
78 | WHERE database_id BETWEEN 5 and 32760
79 | )
80 | SELECT database_id
81 | , database_name
82 | , page_type_and_level
83 | , COUNT(*) AS pages
84 | , COUNT(*)*8/1024 AS mb_total
85 | , SUM(free_space_in_bytes)/1024/1024 AS mb_free
86 | FROM data
87 | GROUP BY database_id
88 | , database_name
89 | , page_type_and_level
90 | , order_id
91 | ORDER BY database_id
92 | , order_id;
93 |
94 |
95 | /*
96 | Show one line per database and custom page type group.
97 | Like the previous statement, but we now focus on fragmentation.
98 | Filtering out "other_page" and small mb_free.
99 | Sorting by mb_free descending.
100 | */
101 | WITH data AS (
102 | SELECT database_id
103 | , DB_NAME(database_id) AS database_name
104 | , CASE
105 | WHEN page_type = 'DATA_PAGE'
106 | THEN 'DATA_PAGE'
107 | WHEN page_type = 'INDEX_PAGE'
108 | AND page_level = 0
109 | THEN 'INDEX_PAGE_leaf'
110 | ELSE 'INDEX_PAGE_non_leaf'
111 | END AS page_type_and_level
112 | , CAST(free_space_in_bytes AS bigint) AS free_space_in_bytes
113 | FROM sys.dm_os_buffer_descriptors
114 | WHERE database_id BETWEEN 5 and 32760
115 | AND page_type IN ('DATA_PAGE', 'INDEX_PAGE')
116 | )
117 | SELECT database_id
118 | , database_name
119 | , page_type_and_level
120 | , COUNT(*) AS pages
121 | , COUNT(*)*8/1024 AS mb_total
122 | , SUM(free_space_in_bytes)/1024/1024 AS mb_free
123 | FROM data
124 | GROUP BY database_id
125 | , database_name
126 | , page_type_and_level
127 | HAVING SUM(free_space_in_bytes)/1024/1024 > 100
128 | ORDER BY SUM(free_space_in_bytes) DESC;
129 |
130 |
131 |
132 | /*
133 | Part 2:
134 |
135 | Detailed view of all pages related to one database.
136 |
137 | Must be be executed from the target database context
138 | and shows information per object.
139 | */
140 |
141 | USE test01
142 | GO
143 |
144 |
145 | /*
146 | Show detailed information for "DATA_PAGE" and "INDEX_PAGE_leaf" per index on user objects.
147 | */
148 | SELECT DB_ID() AS database_id
149 | , DB_NAME() AS database_name
150 | , o.name AS table_name
151 | , i.index_id
152 | , i.name AS index_name
153 | , i.type_desc AS index_type
154 | , CASE
155 | WHEN bd.page_type = 'DATA_PAGE'
156 | THEN 'DATA_PAGE'
157 | ELSE 'INDEX_PAGE_leaf'
158 | END AS page_type_and_level
159 | , COUNT(*) AS pages
160 | , COUNT(*)*8/1024 AS mb_total
161 | , SUM(CAST(bd.free_space_in_bytes AS bigint))/1024/1024 AS mb_free
162 | , AVG(CAST(bd.free_space_in_bytes AS bigint)) AS avg_free_space_in_bytes
163 | , (8096-AVG(CAST(bd.free_space_in_bytes AS bigint)))*100/8096 AS avg_page_space_used_in_percent
164 | , CASE
165 | WHEN i.fill_factor = 0
166 | THEN 100
167 | ELSE i.fill_factor
168 | END AS index_fill_factor
169 | FROM sys.dm_os_buffer_descriptors AS bd
170 | JOIN sys.allocation_units AS au ON bd.allocation_unit_id = au.allocation_unit_id
171 | JOIN sys.partitions AS p ON au.container_id = p.hobt_id
172 | JOIN sys.objects AS o ON p.object_id = o.object_id
173 | JOIN sys.indexes AS i ON p.object_id = i.object_id AND p.index_id = i.index_id
174 | WHERE bd.database_id = DB_ID()
175 | AND bd.page_type IN ('DATA_PAGE', 'INDEX_PAGE')
176 | AND bd.page_level = 0
177 | AND au.type_desc = 'IN_ROW_DATA'
178 | AND o.is_ms_shipped = 0
179 | GROUP BY o.name
180 | , i.index_id
181 | , i.name
182 | , i.type_desc
183 | , i.fill_factor
184 | , bd.page_type
185 | -- Sort order 1: By table_name and index_id
186 | ORDER BY o.name, i.index_id;
187 | -- Sort order 2: By mb_free descending
188 | ORDER BY SUM(CAST(bd.free_space_in_bytes AS bigint)) DESC;
189 |
190 |
191 | /*
192 | Show detailed information of all pages not shown in previous query.
193 | */
194 | SELECT DB_ID() AS database_id
195 | , DB_NAME() AS database_name
196 | , o.is_ms_shipped
197 | , o.name AS table_name
198 | , i.index_id
199 | , i.name AS index_name
200 | , i.type_desc AS index_type
201 | , bd.page_type AS page_type
202 | , bd.page_level
203 | , au.type_desc AS allocation_type
204 | , COUNT(*) AS pages
205 | , COUNT(*)*8/1024 AS mb_total
206 | FROM sys.dm_os_buffer_descriptors AS bd
207 | LEFT JOIN sys.allocation_units AS au ON bd.allocation_unit_id = au.allocation_unit_id
208 | LEFT JOIN sys.partitions AS p ON au.container_id = p.hobt_id
209 | LEFT JOIN sys.objects AS o ON p.object_id = o.object_id
210 | LEFT JOIN sys.indexes AS i ON p.object_id = i.object_id AND p.index_id = i.index_id
211 | WHERE bd.database_id = DB_ID()
212 | AND NOT ( ISNULL(bd.page_type, 'X') IN ('DATA_PAGE', 'INDEX_PAGE')
213 | AND ISNULL(bd.page_level, -1) = 0
214 | AND ISNULL(au.type_desc, 'X') = 'IN_ROW_DATA'
215 | AND ISNULL(o.is_ms_shipped, -1) = 0
216 | )
217 | GROUP BY o.is_ms_shipped
218 | , o.name
219 | , i.index_id
220 | , i.name
221 | , i.type_desc
222 | , bd.page_type
223 | , bd.page_level
224 | , au.type_desc
225 | ORDER BY 1,2,3,4,5,6,7,8,9,10;
226 |
227 |
228 | /*
229 | Very detailed view, query should be customized for individual needs.
230 | */
231 | SELECT DB_ID() AS database_id
232 | , DB_NAME() AS database_name
233 | , o.name AS table_name
234 | , i.name AS index_name
235 | , i.type_desc AS index_type
236 | , i.fill_factor
237 | , bd.page_type
238 | , au.type_desc AS allocation_type
239 | , bd.file_id
240 | , bd.page_id
241 | , bd.row_count
242 | , bd.free_space_in_bytes
243 | , bd.page_level
244 | FROM sys.dm_os_buffer_descriptors AS bd
245 | JOIN sys.allocation_units AS au ON bd.allocation_unit_id = au.allocation_unit_id
246 | JOIN sys.partitions AS p ON au.container_id = p.hobt_id
247 | JOIN sys.objects AS o ON p.object_id = o.object_id
248 | JOIN sys.indexes AS i ON p.object_id = i.object_id AND p.index_id = i.index_id
249 | WHERE bd.database_id = DB_ID()
250 | -- AND bd.page_type IN ('DATA_PAGE', 'INDEX_PAGE')
251 | -- AND bd.page_level = 0
252 | -- AND au.type_desc = 'IN_ROW_DATA'
253 | -- AND o.is_ms_shipped = 0
254 | ORDER BY 1,2,3,4,5,6,7,8,9;
255 |
256 |
--------------------------------------------------------------------------------
/SQLServer/Fragmentation_createDemoData.sql:
--------------------------------------------------------------------------------
1 | USE master
2 | GO
3 |
4 | DROP DATABASE TestDB
5 | GO
6 |
7 | CREATE DATABASE TestDB
8 | GO
9 |
10 | ALTER DATABASE TestDB SET RECOVERY SIMPLE;
11 | GO
12 |
13 | USE TestDB
14 | GO
15 |
16 | SET NOCOUNT ON;
17 | GO
18 |
19 | CREATE TABLE dbo.TestTableA
20 | ( Id int IDENTITY
21 | , JustSpace varchar(300) DEFAULT REPLICATE('Space', 30) -- 5x 30 = 150 BYTE
22 | , CONSTRAINT TestTableA_PK PRIMARY KEY (Id)
23 | );
24 | GO
25 |
26 | CREATE TABLE dbo.TestTableB
27 | ( Id int IDENTITY
28 | , JustSpace varchar(300) DEFAULT REPLICATE('Space', 40) -- 5x 40 = 200 BYTE
29 | , CONSTRAINT TestTableB_PK PRIMARY KEY (Id)
30 | );
31 | GO
32 |
33 | CREATE TABLE dbo.TestTableC
34 | ( Id int IDENTITY
35 | , JustSpace varchar(300) DEFAULT REPLICATE('Space', 60) -- 5x 60 = 300 BYTE
36 | , CONSTRAINT TestTableC_PK PRIMARY KEY (Id)
37 | );
38 | GO
39 |
40 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
41 | INSERT INTO dbo.TestTableB DEFAULT VALUES;
42 | INSERT INTO dbo.TestTableC DEFAULT VALUES;
43 | GO 100000
44 |
45 | UPDATE dbo.TestTableA
46 | SET JustSpace = JustSpace + 'we need much more Space!!we need much more Space!!' -- 150 -> 200 BYTE
47 | WHERE Id <= 30000;
48 |
49 | UPDATE dbo.TestTableB
50 | SET JustSpace = SUBSTRING(JustSpace, 1, 150) -- 200 -> 150 BYTE
51 | WHERE Id > 30000;
52 |
53 | UPDATE dbo.TestTableC
54 | SET JustSpace = SUBSTRING(JustSpace, 1, 200) -- 300 -> 200 BYTE
55 | WHERE Id <= 30000;
56 |
57 | UPDATE dbo.TestTableC
58 | SET JustSpace = SUBSTRING(JustSpace, 1, 150) -- 300 -> 150 BYTE
59 | WHERE Id > 30000;
60 |
61 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
62 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
63 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
64 | AND index_level = 0;
65 |
66 | /*
67 | TestTableA 1 46,32 1437 2709 82,98 180 100000
68 | TestTableB 1 0,33 347 2703 83,16 180 100000
69 | TestTableC 1 0,35 434 4000 56,19 180 100000
70 |
71 | Test table A and B have both filled their pages to around 80 percent, but only A has a high value in avg_fragmentation_in_percent and would be processed by most de-fragmentation tools.
72 | Test table C has their pages only filled around 50 percent, but as the value of avg_fragmentation_in_percent is so low, it would not be processed by most de-fragmentation tools.
73 | */
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | USE master
83 | GO
84 |
85 | DROP DATABASE TestDB
86 | GO
87 |
88 | CREATE DATABASE TestDB
89 | GO
90 |
91 | ALTER DATABASE TestDB SET RECOVERY SIMPLE;
92 | GO
93 |
94 | USE TestDB
95 | GO
96 |
97 | SET NOCOUNT ON;
98 | GO
99 |
100 | CREATE TABLE dbo.TestTableA
101 | ( Id int IDENTITY
102 | , Number decimal(10,10) DEFAULT RAND()
103 | , String char(30) DEFAULT SUBSTRING(CONVERT(varchar, RAND()*10000000, 3), 3, 15) + SUBSTRING(CONVERT(varchar, RAND()*10000000, 3), 3, 15)
104 | , JustSpace varchar(300) DEFAULT REPLICATE('Space', 30) -- 5x 30 = 150 BYTE
105 | , CONSTRAINT TestTableA_PK PRIMARY KEY (Id)
106 | );
107 | GO
108 |
109 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
110 | GO 95000
111 |
112 | CREATE INDEX TestIndex ON dbo.TestTableA (String) WITH FILLFACTOR = 90
113 | GO
114 |
115 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
116 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
117 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
118 | AND index_id = 2
119 | AND index_level = 0;
120 | GO
121 |
122 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
123 | GO 5000
124 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 5000
125 | GO
126 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
127 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
128 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
129 | AND index_id = 2
130 | AND index_level = 0;
131 | GO
132 |
133 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
134 | GO 5000
135 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 5000
136 | GO
137 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
138 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
139 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
140 | AND index_id = 2
141 | AND index_level = 0;
142 | GO
143 |
144 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
145 | GO 5000
146 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 5000
147 | GO
148 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
149 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
150 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
151 | AND index_id = 2
152 | AND index_level = 0;
153 | GO
154 |
155 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
156 | GO 5000
157 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 5000
158 | GO
159 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
160 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
161 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
162 | AND index_id = 2
163 | AND index_level = 0;
164 | GO
165 |
166 | INSERT INTO dbo.TestTableA DEFAULT VALUES;
167 | GO 5000
168 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 5000
169 | GO
170 |
171 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
172 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
173 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
174 | AND index_id = 2
175 | AND index_level = 0;
176 | GO
177 |
178 | --ALTER INDEX TestIndex ON dbo.TestTableA REBUILD
179 | --GO
180 |
181 | DELETE dbo.TestTableA WHERE Id < (SELECT MIN(Id) FROM dbo.TestTableA) + 25000
182 | GO
183 |
184 | SELECT OBJECT_NAME(object_id) table_name, index_id, avg_fragmentation_in_percent, fragment_count, page_count, avg_page_space_used_in_percent, avg_record_size_in_bytes, record_count
185 | FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
186 | WHERE OBJECT_NAME(object_id) LIKE 'Test%'
187 | AND index_id = 2
188 | AND index_level = 0;
189 | GO
190 |
191 |
192 |
193 |
194 | --SELECT * FROM dbo.TestTableA;
195 |
196 | --UPDATE dbo.TestTableA SET String = REVERSE(String) WHERE Number < 0.3
197 |
198 |
--------------------------------------------------------------------------------
/dbatools/Get-CU.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param (
3 | [ValidateSet('2022', '2019', '2017', '2016', '2014')]
4 | [string[]]$Version = @('2022', '2019', '2017'),
5 | [int]$Last = 1,
6 | [string]$Path = '.',
7 | [switch]$UpdateBuildReference = $true
8 | )
9 |
10 | function Get-CU {
11 | param(
12 | [Parameter(Mandatory)]
13 | [ValidateSet('2022', '2019', '2017', '2016', '2014')]
14 | [string]$Version,
15 | [regex]$BuildBaseRegex,
16 | [int]$Last = 1,
17 | [string[]]$Exclude,
18 | [string]$Path = '.'
19 | )
20 | if ($null -eq $BuildBaseRegex) {
21 | $BuildBaseRegex = switch ($Version) {
22 | '2022' { '^16' }
23 | '2019' { '^15' }
24 | '2017' { '^14' }
25 | '2016' { '^13.0.5' } # Based on SP2
26 | '2014' { '^12.0.6' } # Based on SP3
27 | }
28 | }
29 | if ($Version -eq '2019') {
30 | $Exclude += 'CU7' # CU7 is not available any more
31 | }
32 | $buildrefFile = Join-Path -Path (Get-DbatoolsConfigValue -Name 'Path.DbatoolsData') -ChildPath "dbatools-buildref-index.json"
33 | $buildrefData = (Get-Content -Path $buildrefFile -Raw | ConvertFrom-Json).Data
34 | $cuData = $buildrefData |
35 | Where-Object -FilterScript { $_.Version -match $BuildBaseRegex -and $_.CU -ne $null -and $_.CU -notin $Exclude } |
36 | Sort-Object -Property KBList |
37 | Select-Object -Last $Last
38 | foreach ($cu in $cuData) {
39 | $kbNr = $cu.KBList
40 | $cuName = $cu.CU
41 | $filePath = Join-Path -Path $Path -ChildPath "SQLServer$Version-KB$kbNr-$cuName-x64.exe"
42 | if (-not (Test-Path -Path $filePath)) {
43 | Write-Progress -Activity "Downloading Cumulative Updates for SQL Server" -Status "Downloading KB $kbNr for SQL Server $Version to $filePath"
44 | Save-DbaKbUpdate -Name $kbNr -FilePath $filePath
45 | }
46 | }
47 | }
48 |
49 | Write-Progress -Activity "Downloading Cumulative Updates for SQL Server" -Status "Importing dbatools"
50 | Import-Module -Name dbatools 6> $null
51 |
52 | if ($UpdateBuildReference) {
53 | Write-Progress -Activity "Downloading Cumulative Updates for SQL Server" -Status "Updating build reference"
54 | Update-DbaBuildReference
55 | }
56 |
57 | foreach ($ver in $Version) {
58 | Get-CU -Version $ver -Last $Last -Path $Path
59 | }
60 |
61 | Write-Progress -Activity "Downloading Cumulative Updates for SQL Server" -Completed
62 |
63 |
64 | <# Other usage examples:
65 |
66 | Get-CU -Version 2019 -Path 'C:\SQLServerPatches'
67 | Get-CU -Version 2017 -Path 'C:\SQLServerPatches' -Last 5
68 | Get-CU -Version 2016 -Path 'C:\SQLServerPatches' -Last 5
69 | Get-CU -Version 2014 -Path 'C:\SQLServerPatches'
70 | # KB4583462 - Security update for SQL Server 2014 SP3 CU4: January 12, 2021
71 | Save-DbaKbUpdate -Name 4583462 -FilePath 'C:\SQLServerPatches\SQLServer2014-KB4583462-CU4-Security-x64.exe'
72 | # KB4583465 - Security update for SQL Server 2012 SP4 GDR: January 12, 2021
73 | Save-DbaKbUpdate -Name 4583465 -FilePath 'C:\SQLServerPatches\SQLServer2012-KB4583465-GDR-Security-x64.exe'
74 | # Check for new versions at: https://sqlserverbuilds.blogspot.com/
75 | # KB5003830 - New CU that is not yet known by dbatools
76 | Save-DbaKbUpdate -Name 5003830 -FilePath 'C:\SQLServerPatches\SQLServer2017-KB5003830-CU25-x64.exe'
77 |
78 | #>
79 |
--------------------------------------------------------------------------------
/dbatools/downgrade_database.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | This demo shows how to downgrade the database StackOverflowMini from version 2022 to an earlier version.
4 |
5 | Get the database from here: https://github.com/BrentOzarULTD/Stack-Overflow-Database
6 |
7 | This could also help you downgrade other databases, but changes may be required.
8 |
9 | #>
10 |
11 |
12 | Import-Module -Name dbatools
13 |
14 | # Connect to the source database.
15 | # You may have to add -SqlCredential if using integrated security is not possible.
16 | $sourceDatabase = Get-DbaDatabase -SqlInstance SQL01\SQL2022 -Database StackOverflowMini
17 |
18 | # Connect to the target instance.
19 | # You may have to add -SqlCredential if using integrated security is not possible.
20 | $targetServer = Connect-DbaInstance -SqlInstance SQL01\SQL2019
21 |
22 | # Define the parameters for the target database based on the source database.
23 | $targetDatabaseParams = @{
24 | SqlInstance = $targetServer
25 | Name = $sourceDatabase.Name
26 | Collation = $sourceDatabase.Collation
27 | PrimaryFilesize = $sourceDatabase.FileGroups['PRIMARY'].Files[0].Size / 1024
28 | }
29 |
30 | # Create the new database on the target instance.
31 | $null = New-DbaDatabase @targetDatabaseParams
32 |
33 | # Copy all the tables including the data.
34 | $transferParams = @{
35 | SqlInstance = $sourceDatabase.Parent
36 | DestinationSqlInstance = $targetServer
37 | Database = $sourceDatabase.Name
38 | CopyAll = 'Tables'
39 | }
40 | Invoke-DbaDbTransfer @transferParams
41 |
--------------------------------------------------------------------------------
/sp_Blitz_markdown/README.md:
--------------------------------------------------------------------------------
1 | # Output results of sp_Blitz in Markdown format
2 |
3 | As `sp_Blitz @OutputType = 'markdown'` excludes all the security findings, I wanted to convert the T-SQL code from inside of sp_Blitz into PowerShell and make it even more flexible.
4 |
5 |
6 |
7 | Here is the T-SQL code:
8 | https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/99c3073b26d255fde928ef0e3076e7600e687596/sp_Blitz.sql#L9375-L9395
9 |
10 |
11 |
12 | And here is the code you also find in the sp_Blitz_output_as_markdown.ps1:
13 |
14 | ```powershell
15 | # Some configuration options. Just play with them and see how they work.
16 | $newLine = "`r`n"
17 | $noNewLineForOneFinding = $true
18 | $noNewLineForOneDatabase = $true
19 |
20 | # I love dbatools, so I use Invoke-DbaQuery to get the data. But Invoke-Sqlcmd would probably work as well.
21 | $sqlInstance = 'sql01'
22 | $spBlitzOutput = Invoke-DbaQuery -SqlInstance $sqlInstance -Query 'sp_Blitz @CheckServerInfo = 1'
23 |
24 | # We filter the data the same way "sp_Blitz @OutputType = 'markdown'" would do, but still include the security findings. You can adjust this if needed.
25 | $spBlitzOutput = $spBlitzOutput | Where-Object -FilterScript { $_.Priority -gt 0 -and $_.Priority -lt 255 -and $_.FindingsGroup -ne [DBNull]::Value -and $_.Finding -ne [DBNull]::Value }
26 |
27 | # Let's start with a blank output string.
28 | $output = ''
29 |
30 | # First we group by both Priority and FindingsGroup and loop through the groups.
31 | $groupsForFindingsGroup = $spBlitzOutput | Group-Object Priority, FindingsGroup
32 | foreach ($groupForFindingsGroup in $groupsForFindingsGroup) {
33 | # The name of the group contains both Priority and FindingsGroup in one string,
34 | # so we have to split and rearrange.
35 | ($priority, $findingsGroup) = $groupForFindingsGroup.Name -split ', '
36 | $output += '**Priority ' + $priority + ': ' + $findingsGroup + '**' + $newLine
37 |
38 | # Second we group on Finding and loop through the groups.
39 | $groupsForFinding = $groupForFindingsGroup.Group | Group-Object Finding
40 | foreach ($groupForFinding in $groupsForFinding) {
41 | # We just output the Finding as an item in a bulleted list.
42 | $finding = $groupForFinding.Name
43 | $output += '- ' + $finding + $newLine
44 |
45 | # Third we group on Details and loop through the groups.
46 | # Here it is important to get an array of groups, even if we have one group,
47 | # because we need the Count as number of groups in the array
48 | # and not the number of members in the group.
49 | # So we start with an empty array and add the groups to it.
50 | # (Declaring the variable would be another way, just go for it if you like.)
51 | $groupsForDetails = @( )
52 | $groupsForDetails += $groupForFinding.Group | Group-Object Details
53 | $groupsForDetailsCount = $groupsForDetails.Count
54 | foreach ($groupForDetails in $groupsForDetails) {
55 | $details = $groupForDetails.Name
56 | # Depending on what you have configured, if we only have one Details group,
57 | # we may just append the Details to the previous line with the Finding.
58 | # Otherwise we output the Details as a bulleted list one level below the Finding.
59 | if ($groupsForDetailsCount -eq 1 -and $noNewLineForOneFinding) {
60 | $output = $output.TrimEnd($newLine) + ': ' + $details + $newLine
61 | } else {
62 | $output += ' - ' + $details + $newLine
63 | }
64 |
65 | # Forth we group on DatabaseName and loop through the groups.
66 | # Again we need the number of groups to be able to append the previous line.
67 | $groupsForDatabaseName = @( )
68 | $groupsForDatabaseName += $groupForDetails.Group | Group-Object DatabaseName | Where-Object Name
69 | $groupsForDatabaseNameCount = $groupsForDatabaseName.Count
70 | foreach ($groupForDatabaseName in $groupsForDatabaseName) {
71 | $databaseName = $groupForDatabaseName.Name
72 | if ($groupsForDatabaseNameCount -eq 1 -and $noNewLineForOneDatabase) {
73 | $output = $output.TrimEnd($newLine) + ' (' + $databaseName + ')' + $newLine
74 | } else {
75 | $output += ' * ' + $databaseName + $newLine
76 | }
77 | }
78 | }
79 | }
80 | # To have an empty line between the different FindingsGroups, we need three empty lines.
81 | $output += $newLine + $newLine + $newLine
82 | }
83 | # Just trim the empty lines at the end and copy the output to the clipboard.
84 | $output = $output.TrimEnd($newLine)
85 | $output | Set-Clipboard
86 |
87 | # Now just paste the clipboard into you favorite markdown editor.
88 | # If you don't have one yet, have a look at Typora or just use Notepad or Notepad++.
89 | ```
90 |
91 |
92 |
93 | Have fun with the code. Feedback is welcome.
94 |
--------------------------------------------------------------------------------
/sp_Blitz_markdown/sp_Blitz_output_as_markdown.ps1:
--------------------------------------------------------------------------------
1 | # Some configuration options. Just play with them and see how they work.
2 | $newLine = "`r`n"
3 | $noNewLineForOneFinding = $true
4 | $noNewLineForOneDatabase = $true
5 |
6 | # I love dbatools, so I use Invoke-DbaQuery to get the data. But Invoke-Sqlcmd would probably work as well.
7 | $sqlInstance = 'sql01'
8 | $spBlitzOutput = Invoke-DbaQuery -SqlInstance $sqlInstance -Query 'sp_Blitz @CheckServerInfo = 1'
9 |
10 | # We filter the data the same way "sp_Blitz @OutputType = 'markdown'" would do, but still include the security findings. You can adjust this if needed.
11 | $spBlitzOutput = $spBlitzOutput | Where-Object -FilterScript { $_.Priority -gt 0 -and $_.Priority -lt 255 -and $_.FindingsGroup -ne [DBNull]::Value -and $_.Finding -ne [DBNull]::Value }
12 |
13 | # Let's start with a blank output string.
14 | $output = ''
15 |
16 | # First we group by both Priority and FindingsGroup and loop through the groups.
17 | $groupsForFindingsGroup = $spBlitzOutput | Group-Object Priority, FindingsGroup
18 | foreach ($groupForFindingsGroup in $groupsForFindingsGroup) {
19 | # The name of the group contains both Priority and FindingsGroup in one string,
20 | # so we have to split and rearrange.
21 | ($priority, $findingsGroup) = $groupForFindingsGroup.Name -split ', '
22 | $output += '**Priority ' + $priority + ': ' + $findingsGroup + '**' + $newLine
23 |
24 | # Second we group on Finding and loop through the groups.
25 | $groupsForFinding = $groupForFindingsGroup.Group | Group-Object Finding
26 | foreach ($groupForFinding in $groupsForFinding) {
27 | # We just output the Finding as an item in a bulleted list.
28 | $finding = $groupForFinding.Name
29 | $output += '- ' + $finding + $newLine
30 |
31 | # Third we group on Details and loop through the groups.
32 | # Here it is important to get an array of groups, even if we have one group,
33 | # because we need the Count as number of groups in the array
34 | # and not the number of members in the group.
35 | # So we start with an empty array and add the groups to it.
36 | # (Declaring the variable would be another way, just go for it if you like.)
37 | $groupsForDetails = @( )
38 | $groupsForDetails += $groupForFinding.Group | Group-Object Details
39 | $groupsForDetailsCount = $groupsForDetails.Count
40 | foreach ($groupForDetails in $groupsForDetails) {
41 | $details = $groupForDetails.Name
42 | # Depending on what you have configured, if we only have one Details group,
43 | # we may just append the Details to the previous line with the Finding.
44 | # Otherwise we output the Details as a bulleted list one level below the Finding.
45 | if ($groupsForDetailsCount -eq 1 -and $noNewLineForOneFinding) {
46 | $output = $output.TrimEnd($newLine) + ': ' + $details + $newLine
47 | } else {
48 | $output += ' - ' + $details + $newLine
49 | }
50 |
51 | # Forth we group on DatabaseName and loop through the groups.
52 | # Again we need the number of groups to be able to append the previous line.
53 | $groupsForDatabaseName = @( )
54 | $groupsForDatabaseName += $groupForDetails.Group | Group-Object DatabaseName | Where-Object Name
55 | $groupsForDatabaseNameCount = $groupsForDatabaseName.Count
56 | foreach ($groupForDatabaseName in $groupsForDatabaseName) {
57 | $databaseName = $groupForDatabaseName.Name
58 | if ($groupsForDatabaseNameCount -eq 1 -and $noNewLineForOneDatabase) {
59 | $output = $output.TrimEnd($newLine) + ' (' + $databaseName + ')' + $newLine
60 | } else {
61 | $output += ' * ' + $databaseName + $newLine
62 | }
63 | }
64 | }
65 | }
66 | # To have an empty line between the different FindingsGroups, we need three empty lines.
67 | $output += $newLine + $newLine + $newLine
68 | }
69 | # Just trim the empty lines at the end and copy the output to the clipboard.
70 | $output = $output.TrimEnd($newLine)
71 | $output | Set-Clipboard
72 |
73 | # Now just paste the clipboard into you favorite markdown editor.
74 | # If you don't have one yet, have a look at Typora or just use Notepad or Notepad++.
75 |
--------------------------------------------------------------------------------