functions/Test-DbaDiskAllocation.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
function Test-DbaDiskAllocation {
    <#
  .SYNOPSIS
   Checks all disks on a computer to see if they are formatted with allocation units of 64KB.
    
  .DESCRIPTION
   Checks all disks on a computer for disk allocation units that match best practice recommendations. If one server is checked, only $true or $false is returned. If multiple servers are checked, each server's name and an IsBestPractice field are returned.
    
   Specify -Detailed for details.
    
   References:
   https://technet.microsoft.com/en-us/library/dd758814(v=sql.100).aspx - "The performance question here is usually not one of correlation per the formula, but whether the cluster size has been explicitly defined at 64 KB, which is a best practice for SQL Server."
    
   http://tk.azurewebsites.net/2012/08/
    
  .PARAMETER ComputerName
   The server(s) to check disk configuration on.
 
  .PARAMETER NoSqlCheck
   If this switch is enabled, the disk(s) will not be checked for SQL Server data or log files.
    
  .PARAMETER SqlCredential
   Allows you to login to servers using SQL Logins instead of Windows Authentication (AKA Integrated or Trusted). To use:
 
   $scred = Get-Credential, then pass $scred object to the -SqlCredential parameter.
 
   Windows Authentication will be used if SqlCredential is not specified. SQL Server does not accept Windows credentials being passed as credentials.
 
   To connect as a different Windows user, run PowerShell as that user.        
   
  .PARAMETER Detailed
   If this switch is enabled, detailed output is produced.
 
  .PARAMETER WhatIf
   If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
  .PARAMETER Confirm
   If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
  .PARAMETER Silent
   If this switch is enabled, the internal messaging functions will be silenced.    
 
  .NOTES
   Tags: CIM, Storage
   Requires: Windows sysadmin access on SQL Servers
     
   dbatools PowerShell module (https://dbatools.io, clemaire@gmail.com)
   Copyright (C) 2016 Chrissy LeMaire
   License: GNU GPL v3 https://opensource.org/licenses/GPL-3.0
 
  .LINK
   https://dbatools.io/Test-DbaDiskAllocation
 
  .EXAMPLE
   Test-DbaDiskAllocation -ComputerName sqlserver2014a
 
   Scans all disks on server sqlserver2014a for best practice allocation unit size.
 
  .EXAMPLE
   Test-DbaDiskAllocation -ComputerName sqlserver2014 -Detailed
    
   Scans all disks on server sqlserver2014a for allocation unit size and returns detailed results for each.
    
  .EXAMPLE
   Test-DbaDiskAllocation -ComputerName sqlserver2014a -NoSqlCheck
 
   Scans all disks not hosting SQL Server data or log files on server sqlserver2014a for best practice allocation unit size.
 #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType("System.Collections.ArrayList", "System.Boolean")]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer", "SqlInstance")]
        [object[]]$ComputerName,
        [switch]$NoSqlCheck,
        [object]$SqlCredential,
        [switch]$Detailed,
        [switch]$Silent
    )
    
    begin {
        if ($Detailed) {
            Write-Message -Level Warning -Message "Detailed is deprecated and will be removed in dbatools 1.0."
        }
        
        $sessionoptions = New-CimSessionOption -Protocol DCOM
        
        function Get-AllDiskAllocation {
            $alldisks = @()
            $SqlInstances = @()
            
            try {
                Write-Message -Level Verbose -Message "Getting disk information from $computer."
                
                # $query = "Select Label, BlockSize, Name from Win32_Volume WHERE FileSystem='NTFS'"
                # $disks = Get-WmiObject -ComputerName $ipaddr -Query $query | Sort-Object -Property Name
                $disks = Get-CimInstance -CimSession $CIMsession -ClassName win32_volume -Filter "FileSystem='NTFS'" -ErrorAction Stop | Sort-Object -Property Name
            }
            catch {
                Stop-Function -Message "Can't connect to WMI on $computer."
                return
            }
            
            if ($NoSqlCheck -eq $false) {
                Write-Message -Level Verbose -Message "Checking for SQL Services"
                $sqlservices = Get-Service -ComputerName $ipaddr | Where-Object { $_.DisplayName -like 'SQL Server (*' }
                foreach ($service in $sqlservices) {
                    $instance = $service.DisplayName.Replace('SQL Server (', '')
                    $instance = $instance.TrimEnd(')')
                    
                    $instancename = $instance.Replace("MSSQLSERVER", "Default")
                    Write-Message -Level Verbose -Message "Found instance $instancename."
                    
                    if ($instance -eq 'MSSQLSERVER') {
                        $SqlInstances += $ipaddr
                    }
                    else {
                        $SqlInstances += "$ipaddr\$instance"
                    }
                }
                $sqlcount = $SqlInstances.Count
                
                Write-Message -Level Verbose -Message "$sqlcount instance(s) found."
            }
            
            foreach ($disk in $disks) {
                if (!$disk.name.StartsWith("\\")) {
                    $diskname = $disk.Name
                    
                    if ($NoSqlCheck -eq $false) {
                        $sqldisk = $false
                        
                        foreach ($SqlInstance in $SqlInstances) {
                            Write-Message -Level Verbose -Message "Connecting to SQL instance ($SqlInstance)."
                            try {
                                $smoserver = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
                                $sql = "Select count(*) as Count from sys.master_files where physical_name like '$diskname%'"
                                $sqlcount = $smoserver.Databases['master'].ExecuteWithResults($sql).Tables[0].Count
                                if ($sqlcount -gt 0) {
                                    $sqldisk = $true
                                    break
                                }
                            }
                            catch {
                                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                                continue
                            }
                        }
                    }
                    
                    if ($disk.BlockSize -eq 65536) {
                        $IsBestPractice = $true
                    }
                    else {
                        $IsBestPractice = $false
                    }
                    
                    $windowsdrive = "$env:SystemDrive\"
                    
                    if ($diskname -eq $windowsdrive) {
                        $IsBestPractice = $false
                    }
                    
                    if ($NoSqlCheck -eq $false) {
                        $alldisks += [PSCustomObject]@{
                            Server         = $computer
                            Name           = $diskname
                            Label          = $disk.Label
                            BlockSize      = $disk.BlockSize
                            IsSqlDisk      = $sqldisk
                            IsBestPractice = $IsBestPractice
                        }
                    }
                    else {
                        $alldisks += [PSCustomObject]@{
                            Server         = $computer
                            Name           = $diskname
                            Label          = $disk.Label
                            BlockSize      = $disk.BlockSize
                            IsBestPractice = $IsBestPractice
                        }
                    }
                }
            }
            return $alldisks
        }
    }
    
    process {
        foreach ($computer in $ComputerName) {
            
            $computer = Resolve-DbaNetworkName -ComputerName $computer -Credential $credential
            $ipaddr = $computer.IpAddress
            $Computer = $computer.ComputerName
            
            if (!$Computer) {
                Stop-Function -Message "Couldn't resolve hostname. Skipping." -Continue
            }
            
            Write-Message -Level Verbose -Message "Creating CimSession on $computer over WSMan."
            
            if (!$Credential) {
                $cimsession = New-CimSession -ComputerName $Computer -ErrorAction SilentlyContinue
            }
            else {
                $cimsession = New-CimSession -ComputerName $Computer -ErrorAction SilentlyContinue -Credential $Credential
            }
            
            if ($null -eq $cimsession.id) {
                Write-Message -Level Verbose -Message "Creating CimSession on $computer over WSMan failed. Creating CimSession on $computer over DCOM."
                
                if (!$Credential) {
                    $cimsession = New-CimSession -ComputerName $Computer -SessionOption $sessionoption -ErrorAction SilentlyContinue -Credential $Credential
                }
                else {
                    $cimsession = New-CimSession -ComputerName $Computer -SessionOption $sessionoption -ErrorAction SilentlyContinue
                }
            }
            
            if ($null -eq $cimsession.id) {
                Stop-Function -Message "Can't create CimSession on $computer" -Target $Computer
            }
            
            Write-Message -Level Verbose -Message "Getting Power Plan information from $Computer"
            
            $data = Get-AllDiskAllocation $computer
                        
            if ($data.Count -gt 1) {
                $data.GetEnumerator()
            }
            else {
                $data
            }
        }
    }
}