How to Use Secure Strings in PowerShell Scripts

How to Use Secure Strings in PowerShell Scripts

In PowerShell, a secure string is a way to store sensitive information, like passwords, in a more secure manner. Secure strings encrypt the data in memory so it’s not stored in plain text.

On occasion you may have the need to write a PowerShell script that will perform an action that requires it to programmatically login to another application by passing a username and password. When you do this it is a bad idea to hard-code sensitive information (like passwords) into the script because anyone who can access the script would also be able to see the password.

So what do you do instead?

Below we will walk through one option that involves converting the password to a secure string and storing it in a text file for reuse later on.

One last caveat before we get into the details: If you are working with any seriously secure data you should be absolutely sure that you fully understand all security risks. Bad people can, unfortunately, be very smart and I am sure that there are risks with ALL methods (this one included). There are people who devote their entire career to keeping bad people out of sensitive information, and they should be consulted if you are trying to secure nuclear codes… or perhaps your grandmother’s secret paprikash recipe.

Save a secure string to a file for later reuse:

"Sup3rS3cr3tP@ssw0rd!" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File "C:\Temp\MySuperPassword.txt"

This will convert the plain text password to an encrypted string of mashed up characters and store it in a text file for reuse later on.

Important to note: The encrypted string is tied to the user and machine that originally encrypted it. This means that you will only be able to decrypt it from the same machine that you originally encrypted it on. This also means that a different user would not be able to decrypt it from any machine.

Retrieve the password from the encrypted file:

ConvertFrom-SecureString -SecureString (Get-Content "C:\Temp\MySuperPassword.txt" | ConvertTo-SecureString) -AsPlainText

This will decrypt the mess of mashed up characters in the text file and give you back a plain text string.

Of course decrypting the string back to plain text defeats some of the purpose of encrypting it in the first place. The better option, if your application is compatible, is to create a PowerShell credential and then pass the credential to the application when logging in. This would keep sensitive password secure as it would not be converted to plain text. Here is an example of creating a credential and passing it to an Invoke-SqlCmd command:

$SecStr = Get-Content "C:\Temp\MySuperPassword.txt" | ConvertTo-SecureString

$Credential = New-Object System.Management.Automation.PSCredential ("MyUserName", $SecStr)

Invoke-SqlCmd -HostName "MyServerName" -Database "master" -Credential $Credential -TrustServerCertificate -Query "select * from sys.databases" | Out-GridView

Using this method keeps the string from being converted back to plain text which decreases the opportunity for unscrupulous people to do nefarious deeds.

One last thing:

You could also capture sensitive information at runtime and store it in a in-memory secure string like this:

$SecStr = Read-Host "Enter your password" -AsSecureString

This will cause the script to pause and wait for input. Whatever the user types in will be stored in an in-memory secure string which can then be passed to a credential or used for other purposes.

Backup SQL Server Agent Jobs: PowerShell Automation Tips for SQL Server

Backup SQL Server Agent Jobs: PowerShell Automation Tips for SQL Server

SQL Server Agent jobs are an essential component of managing and automating various database tasks. Whether you’re scheduling backups, running maintenance plans, or executing custom scripts, these jobs are crucial for keeping your SQL Server environment running smoothly. But what happens when you need to back up and recover these jobs? Automating the process of scripting out SQL Server Agent jobs ensures you always have a backup of these critical assets. In this blog post, we’ll explore how to use the SqlServer PowerShell module to automate this process.

Now obviously, since SQL Server stores the Agent jobs in msdb, if you want to be able to include SQL Server Agent jobs in a full restore of a SQL Server then it is crucial that you are including msdb in your regular database backup routines. But let’s say that you also wanted to automate a process of scripting out all of the SQL Server Agent jobs on a SQL Server. There are a number of reasons that you may want to do this, some of which include:

  • Backup and recovery: Safeguard your jobs in case of accidental deletion or server failure
  • Migration: Easily transfer jobs to another SQL Server instance
  • Version Control: Keep track of changes made to your jobs over time

While SQL Server Management Studio (SSMS) allows you to manually script out jobs, automating the process with PowerShell is faster, repeatable, and less error-prone.

Prerequisites

To follow along, ensure the following:

  • PowerShell is installed on your machine.
  • The SqlServer PowerShell module is installed. If not, you can install it by running:
Install-Module -Name SqlServer -Scope CurrentUser
  • You have sufficient permissions to access SQL Server Agent and script out jobs.

Import the SqlServer module and create a SMO object:

Once you have validated that the SqlServer module is installed you can create a new SMO (SQL Management Object) connection to SQL Server by executing the following command in PowerShell:

Import-Module SqlServer
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server "YourSQLServerName"

Non-Standard port: If your SQL Server runs on a non-standard port then you can follow the server name by the port number separated by a comma. Like below:

Import-Module SqlServer
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server "YourSQLServerName,1234"

How let’s list out the jobs:

Once you have created the SMO object to your SQL Server you can list out the jobs that are on the server by running the following command:

$srv.JobServer.Jobs

And from here you can script each job by executing the following command:

$srv.JobServer.Jobs | foreach{
    $_.Script() | Out-File "C:\temp\AllMyJobs.sql" -Append  # put in the path here 
}

However…

It may not be very helpful to have everything all mashed into one big file like a SQL Server Agent stew (probably wouldn’t taste very good anyway!). So instead let’s put everything together and add some magic so that we can loop through each job and create a separate script file for each job.

Here is the script that puts it all together and scripts out each SQL Server Agent job to an individually named SQL script file:

clear-host
#------------------------------------------------------------------------------------------
# The following only needs to be run if the SqlServer module has not been installd yet:
# Install-Module -Name SqlServer -AllowClobber -Scope CurrentUser
#------------------------------------------------------------------------------------------

#------------------------------------------------------------------------------------------
# Define the SQL Server name that you want to script out Agent Jobs for.
# If the server is running on a non-standard port then include the port number separated by a comma.
# For example: "ServerName,1234"
#------------------------------------------------------------------------------------------
$serverName = "ServerName"


#------------------------------------------------------------------------------------------
# Define the base folder where the backup scripts will be stored
# If this path does not exist then the script will attempt to create it.
# ** The final output folder will include a subfolder of this path named for the date and time
# with an additional subfolder named "SqlServerAgentJobs"
# For example, something like this: C:\Temp\ScriptedSQLObjects\2025-01-07_135322\SqlServerAgentJobs\
#------------------------------------------------------------------------------------------
[string]$basePath = "C:\Temp\ScriptedSQLObjects\"    




#------------------------------------------------------------------------------------------
# Import the SQL Server module. 
# ** Remember: this line of code will fail if the SqlServer module has not been installed.
# See the line at the top of the script for syntax for installeing the module
#------------------------------------------------------------------------------------------
Import-Module SqlServer


#------------------------------------------------------------------------------------------
# Create a new SMO connection to the SQL Server
#------------------------------------------------------------------------------------------
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server $serverName

           

#------------------------------------------------------------------------------------------
# Define the final output folder path and attempt to create final output folder
#------------------------------------------------------------------------------------------
[string]$fileSuffix = (Get-Date -Format "yyyy-MM-dd_HHmmss").ToString().Trim()
[string]$outputFolder = [System.IO.Path]::Combine($basePath, $fileSuffix)

$objectFolderPath = [System.IO.Path]::Combine($outputFolder, "SqlServerAgentJobs")
if(!([System.IO.Directory]::Exists($objectFolderPath)))
{
    write-host "Creating directory path $objectFolderPath" -ForegroundColor Cyan
    [System.IO.Directory]::CreateDirectory($objectFolderPath) | out-null
}
write-host "Scripting SQL Server Agent Jobs to the following folder: $objectFolderPath" -ForegroundColor Cyan



#------------------------------------------------------------------------------------------
# Now cycle through the jobs and script out each job
#------------------------------------------------------------------------------------------
[int]$jobCnt = 0

$jobs = $srv.JobServer.Jobs
foreach($job in $jobs)
{
    [string]$objName = $job.Name.ToString().Trim()
    
    [string]$fileName = "Jobs_" + $objName + ".sql"
    $fileName = $fileName.Replace("\","").Replace("/", "").Replace(":", "").Replace(";", "").Replace("'", "").Replace("*", "").Replace("<", "").Replace(">", "").Replace("?", "").Replace("!", "").Replace("$", "").Replace("+", "").Replace("]", "").Replace("[", "")
    [string]$fullPath = join-path $objectFolderPath $fileName

    [string]$separator = "--******************************************************************************`r`n-- Job Name: " + $job.Name.ToString().Trim() + "`r`n-- Job Category: " + $job.Category.ToString().Trim() + "`r`n--******************************************************************************`r`n"
    $separator  | Out-File $fullPath -Append
    $job.Script() | Out-File $fullPath -Append
    "`r`n go `r`n"  | Out-File $fullPath -Append
    $jobCnt++
}

Write-Host "Total Agent Jobs scripted for $serverName is $jobCnt" -ForegroundColor Cyan

Monitoring SQL Server Drive Usage: Three Queries to Get the Job Done (mostly)

Monitoring SQL Server Drive Usage: Three Queries to Get the Job Done (mostly)

Understanding the drive space usage on your SQL Server is essential for maintaining optimal performance and preventing storage-related issues. SQL Server offers several ways to monitor drive usage directly from a query. In this post, we’ll explore three different approaches, each with its unique use case and level of detail.

1. Querying Drive Space Using sys.dm_os_volume_stats

The sys.dm_os_volume_stats dynamic management function is a reliable way to retrieve detailed drive usage statistics for volumes associated with SQL Server database files. This method is particularly useful for DBAs who want a comprehensive view of drive usage. One caveat though: sys.dm_os_volume_status is only going to return information for drives associated with SQL Server database files which, for most SQL Servers, isn’t going to include your operating system drive.

Here’s the query:

select distinct
  a.volume_mount_point
, convert(decimal(20,2), (((a.total_bytes / 1024.0) / 1024.0) / 1024.0)) as [DriveTotalSpace_GB]
, convert(decimal(20,2), (((a.available_bytes / 1024.0) / 1024.0) / 1024.0)) as [DriveAvailableSpace_GB]
, convert(decimal(20,2), ((((a.total_bytes - a.available_bytes) / 1024.0) / 1024.0) / 1024.0)) as [DriveUsedSpace_GB]
, convert(decimal(20,2), ((((a.available_bytes / 1024.0) / 1024.0) / 1024.0) / (((a.total_bytes / 1024.0) / 1024.0) / 1024.0)) * 100) as [DriveAvailablePercentage]
, convert(decimal(20,2), (((((a.total_bytes - a.available_bytes) / 1024.0) / 1024.0) / 1024.0) / (((a.total_bytes / 1024.0) / 1024.0) / 1024.0)) * 100) as [DriveUsedPercentage]
from sys.master_files as [b]
cross apply sys.dm_os_volume_stats(b.database_id,b.[file_id]) as [a];

Output: Provides detailed metrics, including total space, available space, used space, and percentages

Why Use This Query?

  • Provides detailed metrics, including total space, available space, used space, and percentages.
  • Helps monitor drive usage specifically for volumes where SQL Server database files are located.
  • Useful for regular storage audits and performance troubleshooting.

Why NOT Use This Query?

  • Does not display usage statistics for drives that are not associated with a SQL Server database

2. Using xp_fixeddrives to retrieve drive free space

The xp_fixeddrives extended stored procedure is a simpler option for quickly checking free space on all drives accessible to SQL Server. While it doesn’t provide as much detail as sys.dm_os_volume_stats, it’s fast and easy to use.

Here’s the query:

exec xp_fixeddrives;

Output:

The result shows each drive letter along with the free space (in MB) available on that drive.

Why Use This Query?

  • Quick and straightforward for a high-level view of available space.
  • Ideal for basic monitoring or initial troubleshooting.
  • No need to join multiple tables or write complex calculations.

Why NOT use this query?

  • Does not provide total drive size or percentages.
  • Limited to drives accessible to SQL Server.

3. Using sys.dm_os_enumerate_fixed_drives

sys.dm_os_enumerage_fixed_drives is DMV enumerates fixed mounted volumes and returns information about them.

SELECT *
    ,convert(decimal(20,2), (((free_space_in_bytes / 1024.0) / 1024.0) / 1024.0)) 'Free Space (GB)'
FROM sys.dm_os_enumerate_fixed_drives;

Why Use This Query?

  • Quick and straightforward for a high-level view of available space.
  • Ideal for basic monitoring or initial troubleshooting.
  • No need to join multiple tables or write complex calculations.

Why NOT use this query?

  • Although sys.dm_os_enumerate_fixed_drives returns more information than xp_fixeddrives, it still lacks in the sense that it is not going to return total drive size so that you can calculate used percentage.

Conclusion:

At the end of the day these are all quick and easy ways to get drive info from SQL Server… however they all lack in one form or another. There are a plethora of other utilities, including some simple PowerShell commands, that can give you a full picture of your drive usage on your SQL Servers.

Query to get SQL Server Agent job schedules

Retrieving SQL Agent Job Schedule Details in SQL Server

If you’re working with SQL Server Agent jobs, it’s often useful to have a quick way to retrieve schedule details for all jobs in your system. Whether you’re auditing schedules, troubleshooting overlapping jobs, or simply documenting your environment, having this query on hand can save you a lot of time.

Below is a SQL query that returns the schedule details for all SQL Agent jobs in your instance. The query pulls data from system tables in the msdb database, which is where SQL Server stores job and schedule information.

--**************************************************************************************************************************************
-- Get SQL Agent Job Schedules
--**************************************************************************************************************************************
declare @exlcude_disabled_schedules bit = 1, @exclude_disabled_jobs bit = 1

select a.[name] as [job_name], a.[enabled] as [IsJobEnabled], c.[enabled] as [IsScheduleEnabled], suser_sname(a.[owner_sid]) as [owner_name]
, c.[name] as [schedule_name]
, case when b.next_run_date > 0 then msdb.dbo.agent_datetime(b.next_run_date, b.next_run_time)  
    ELSE msdb.dbo.agent_datetime('19000101', b.next_run_time) END as [next_run_dttm]
, msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time) as [active_start_dttm]
, msdb.dbo.agent_datetime(c.active_end_date, c.active_end_time) as [active_end_dttm]

, case c.freq_type when 1 then 'One time only'
                 when 4 then 'Daily'
                 when 8 then 'Weekly'
                 when 16 then 'Monthly'
                 when 32 then 'Monthly, relative to freq_interval'
                 when 64 then 'Runs when SQL Server Agent service starts'
                 when 128 then 'Runs when computer is idle'
                 else 'Unknown'
                 end as [freq_type_descr]
, case when c.freq_type = 1 then 'Unused'
       when c.freq_type = 4 then 'Every ' + convert(varchar(10), c.freq_interval) + ' days'
       when c.freq_type = 8 then 'Weekly: every'
                   + case when c.freq_interval & 1 = 1 then ' Sunday' else '' end 
                   + case when c.freq_interval & 2 = 2 then ' Monday' else '' end 
                   + case when c.freq_interval & 4 = 4 then ' Tuesday' else '' end 
                   + case when c.freq_interval & 8 = 8 then ' Wednesday' else '' end 
                   + case when c.freq_interval & 16 = 16 then ' Thursday' else '' end 
                   + case when c.freq_interval & 32 = 32 then ' Friday' else '' end 
                   + case when c.freq_interval & 64 = 64 then ' Saturday' else '' end 

       when c.freq_type = 16 then 'Monthly: on the ' + convert(varchar(10), c.freq_interval) + ' day of every ' + convert(varchar(10), c.freq_recurrence_factor) + ' month(s)'
       when c.freq_type = 32 then 'Monthly: on the ' + case when c.freq_relative_interval = 0 then 'Unused'
                 when c.freq_relative_interval = 1 then 'First'
                 when c.freq_relative_interval = 2 then 'Second'
                 when c.freq_relative_interval = 4 then 'Third'
                 when c.freq_relative_interval = 8 then 'Fourth'
                 when c.freq_relative_interval = 16 then 'Last'
                 else 'Unknown' end + ' ' + case when c.freq_interval = 1 then  'Sunday'
                                                                when c.freq_interval = 2 then  'Moday'
                                                                when c.freq_interval = 3 then  'Tusday'
                                                                when c.freq_interval = 4 then  'Wednesday'
                                                                when c.freq_interval = 5 then  'Thursday'
                                                                when c.freq_interval = 6 then  'Friday'
                                                                when c.freq_interval = 7 then  'Saturday'
                                                                when c.freq_interval = 8 then  'Day'
                                                                when c.freq_interval = 9 then  'Weekday'
                                                                when c.freq_interval = 10 then 'Weekend day'
                                                                end + ' of every ' + convert(varchar(10), c.freq_recurrence_factor) + ' month(s)'
       else 'Unused'
       end as [freq_interval_descr]
, case when c.freq_type = 1 then 'At the specified time'
       when c.freq_subday_type = 1 then 'At the specified time'
       when c.freq_subday_type = 2 then 'Seconds'
       when c.freq_subday_type = 4 then 'Minutes'
       when c.freq_subday_type = 8 then 'Hours'
                 end as [freq_subday_type_descr]
, case 
when c.freq_type = 1 then 'At ' + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time), 121),12, 12)
when c.freq_subday_type = 1 then 'At ' + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time), 121),12, 12)
when c.freq_subday_type in (2,4,8) then 'Every ' + convert(varchar(10), c.freq_subday_interval) + ' ' + case c.freq_subday_type when 1 then 'At the specified time'
                 when 2 then 'Seconds' + ' between ' + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time), 121),12, 12) + ' and '  + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_end_date, c.active_end_time), 121),12, 12)
                 when 4 then 'Minutes' + ' between ' + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time), 121),12, 12) + ' and '  + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_end_date, c.active_end_time), 121),12, 12)
                 when 8 then 'Hours' + ' between ' + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_start_date, c.active_start_time), 121),12, 12) + ' and '  + substring(convert(varchar(23), msdb.dbo.agent_datetime(c.active_end_date, c.active_end_time), 121),12, 12)
                 end 
                 else 'Unused' end as [freq_subday_interval_descr]
from msdb.dbo.sysjobs as [a]
join msdb.dbo.sysjobschedules as [b] on b.job_id = a.job_id
join msdb.dbo.sysschedules as [c] on c.schedule_id = b.schedule_id and (@exlcude_disabled_schedules = 0 or (@exlcude_disabled_schedules = 1 and c.[enabled] = 1))
where (@exclude_disabled_jobs = 0 or (@exclude_disabled_jobs = 1 and a.[enabled] = 1))
order by a.[name] asc, c.[name] asc

SQL Server – Query to get DDL changes from default trace

Occasionally I need to read the default trace to get recent DDL changes from SQL Server. Here is a handy query that I wrote to read the default trace look for recent DDL changes.

Here is the query that I use:

SELECT
  a.StartTime as [EventTime]
, a.EndTime
, a.ServerName
, b.name as [EventClass_Descr]
, d.name as [Category_Descr]
, c.subclass_name as [EventSubClass_Descr]
, case isnull(a.ObjectType, -123456)
			when 8259 then 'Check Constraint'
			when 8260 then 'Default (constraint or standalone)'
			when 8262 then 'Foreign-key Constraint'
			when 8272 then 'Stored Procedure'
			when 8274 then 'Rule'
			when 8275 then 'System Table'
			when 8276 then 'Trigger on Server'
			when 8277 then '(User-defined) Table'
			when 8278 then 'View'
			when 8280 then 'Extended Stored Procedure'
			when 16724 then 'CLR Trigger'
			when 16964 then 'Database'
			when 16975 then 'Object'
			when 17222 then 'FullText Catalog'
			when 17232 then 'CLR Stored Procedure'
			when 17235 then 'Schema'
			when 17475 then 'Credential'
			when 17491 then 'DDL Event'
			when 17741 then 'Management Event'
			when 17747 then 'Security Event'
			when 17749 then 'User Event'
			when 17985 then 'CLR Aggregate Function'
			when 17993 then 'Inline Table-valued SQL Function'
			when 18000 then 'Partition Function'
			when 18002 then 'Replication Filter Procedure'
			when 18004 then 'Table-valued SQL Function'
			when 18259 then 'Server Role'
			when 18263 then 'Microsoft Windows Group'
			when 19265 then 'Asymmetric Key'
			when 19277 then 'Master Key'
			when 19280 then 'Primary Key'
			when 19283 then 'ObfusKey'
			when 19521 then 'Asymmetric Key Login'
			when 19523 then 'Certificate Login'
			when 19538 then 'Role'
			when 19539 then 'SQL Login'
			when 19543 then 'Windows Login'
			when 20034 then 'Remote Service Binding'
			when 20036 then 'Event Notification on Database'
			when 20037 then 'Event Notification'
			when 20038 then 'Scalar SQL Function'
			when 20047 then 'Event Notification on Object'
			when 20051 then 'Synonym'
			when 20307 then 'Sequence'
			when 20549 then 'End Point'
			when 20801 then 'Adhoc Queries which may be cached'
			when 20816 then 'Prepared Queries which may be cached'
			when 20819 then 'Service Broker Service Queue'
			when 20821 then 'Unique Constraint'
			when 21057 then 'Application Role'
			when 21059 then 'Certificate'
			when 21075 then 'Server'
			when 21076 then 'Transact-SQL Trigger'
			when 21313 then 'Assembly'
			when 21318 then 'CLR Scalar Function'
			when 21321 then 'Inline scalar SQL Function'
			when 21328 then 'Partition Scheme'
			when 21333 then 'User'
			when 21571 then 'Service Broker Service Contract'
			when 21572 then 'Trigger on Database'
			when 21574 then 'CLR Table-valued Function'
			when 21577 then 'Internal Table (For example, XML Node Table, Queue Table.)'
			when 21581 then 'Service Broker Message Type'
			when 21586 then 'Service Broker Route'
			when 21587 then 'Statistics'
			when 21825 then 'User'
			when 21827 then 'User'
			when 21831 then 'User'
			when 21843 then 'User'
			when 21847 then 'User'
			when 22099 then 'Service Broker Service'
			when 22601 then 'Index'
			when 22604 then 'Certificate Login'
			when 22611 then 'XMLSchema'
			when 22868 then 'Type'
            when -123456 then case when a.EventClass in (108, 104) then 'Login' when a.EventClass in (109, 110) then 'User' else 'Unknown' end
            else 'Unknown'
          end as [ObjectType_Description]
, a.DatabaseName
, a.ObjectName
, a.LoginName
, a.NTUserName
, a.NTDomainName
, a.HostName
, a.ApplicationName
, a.DBUserName

, a.TargetUserName
, a.TargetLoginName
, a.RoleName

, convert(nvarchar(max), a.TextData) as [TextData]
, a.DatabaseID
, a.ObjectID        -----------------------
, a.IndexID
, a.Duration

FROM    sys.fn_trace_gettable(CONVERT(nvarchar(256), (SELECT case when charindex('_', convert(nvarchar(256), f.value)) = 0 then convert(nvarchar(256), f.value) else substring(convert(nvarchar(256), f.value), 1, charindex('_', convert(nvarchar(256), f.value))-1) end + '.trc'
                                                      FROM    sys.fn_trace_getinfo(1) f
                                                      WHERE   f.property = 2
                                                    )), DEFAULT) as [a]

        left outer JOIN sys.trace_events as [b] ON a.EventClass = b.trace_event_id
        left outer JOIN sys.trace_subclass_values c ON c.trace_event_id = b.trace_event_id
                                            AND c.subclass_value = a.EventSubClass
		left outer join sys.trace_categories as [d] on d.category_id = b.category_id
WHERE a.DatabaseName <> 'tempdb'
  and isnull(a.EventClass, -123456) in (46, --	Object:Created	Objects
										 47,  --	Object:Deleted	Objects
										 164 -- Object:Altered
)
  and isnull(a.ObjectType,0) <> 21587   -- don't get auto-statistics as it generates too much noise
order by a.StartTime desc

Capture

SQL Server – Find the size of database file – Find the size of log file

As a DBA you may sometimes have the need to gather some stats on the sizes of files in your databases.  Once again Microsoft has been kind enough to provide us with some handy tools get get at this information without having to use the clunky GUI.

If you are just simply looking for the size of the files and are not concerned about finding the used space and free space you can simply use the sys.master_files DMV.  This DMV has a column named “size” which represents the number of 8k pages in the database file.  To get to MB you would simply multiply size times 8 and then divide the total by 1024.  I like to throw in some rounding and conversion to make my result more readable.

Here is an example.

select db_name(database_id) as [DatabaseName]
, name as [Logical_Name]
, convert(decimal(18,2), round(((size * 8) / 1024.00),2)) as [SizeMB]
from sys.master_files
where db_name(database_id) = 'AdventureWorks2014'

And here are the results:

Capture

But what if I also want to determine the used space and free space in the file?

Well, its always been a bit of a source of irritation for me that this information cannot be obtained from sys.master_files.  Just seems like it would have been something that could have been included in this DMV and it would make it so much easier when trying to include this information in reports, queries, etc… but at least there is, in fact, still a pretty simple way to get at this info.

To get database file used space and free space we can use dbcc showfilestats.  When you execute this statement you will see columns named “TotalExtents” and “UsedExtents“.  These columns represent the number of 64k extents in the databse file.  So, for example, if you wanted to convert TotalExtents to MB you would need to multiply TotalExtents by 64 and then divide the total by 1024.   Again, I like to throw in some rounding and conversion to make my result more readable.  Unfortunately dbcc showfilestats will only display information for database files in the current database and it also does not include information about the log files, and since it is a DBCC command it’s not as easy to use it in select statements.  So first we will look at the output from the dbcc command and then I will show you a query that gives you the info in a format that is more useful.

To run this command you would just simply execute the following statement:

dbcc showfilestats with no_infomsgs;

And the result:

Capture2

As you can see, unless you are used to thinking in terms of EXTENTS instead of MB or GB, this isn’t very helpful.  So, as promised, here is another query that will take the results of the dbcc command and dump it into a temp table and then do the conversion for us:


use AdventureWorks2014;
go
if (select object_id('tempdb..#tempFileSize')) is not null
drop table #tempFileSize;

create table #tempFileSize (Fileid int, [FileGroup] int, TotalExtents bigint, UsedExtents bigint, Name nvarchar(128), [FileName] nvarchar(260))

declare @sql nvarchar(max);
set @sql = 'dbcc showfilestats with no_infomsgs;'

insert into #tempFileSize exec(@sql);
select db_name() as [DatabaseName],
 Name as [Logical_Name],
 convert(decimal(18,2), round(((TotalExtents * 64) / 1024.0),2)) as [TotalMB],
 convert(decimal(18,2), round(((UsedExtents * 64) / 1024.0),2)) as [UsedMB],
 convert(decimal(18,2), round((((TotalExtents - UsedExtents) * 64) / 1024.0),2)) as [FreeMB]
from #tempFileSize

And the results:

Capture3

 

 

SQL Server – Query to get drive free space from SQL Server

Question:

How can I query SQL Server to get the drive space free, drive space used and total drive size.

Answer:

Beginning with SQL Server 2008R2 Microsoft introduced a new system DMF (dynamic management function) named sys.dm_os_volume_stats.  This DMF returns information about the operating system volume (directory) on which the specified databases and files are stored in SQL Server. You can use this dynamic management function to check the attributes of the physical disk drive or return available free space information about the directory.

This DMF takes two arguments:

  1. database_id – The ID of the database. database_id is int, with no default. Cannot be NULL.
  2. file_id – The ID of the file. file_id is int, with no default. Cannot be NULL.

You can join this DMF to the sys.master_files DMV to get the drive space usage statistics for the drives where you have a database file stored.


-----------------------------------------

select distinct
convert(varchar(512), b.volume_mount_point) as [volume_mount_point]
, convert(varchar(512), b.logical_volume_name) as [logical_volume_name]
, convert(decimal(18,1), round(((convert(float, b.available_bytes) / convert(float, b.total_bytes)) * 100),1)) as [percent_free]
, convert(bigint, round(((b.available_bytes / 1024.0)/1024.0),0)) as [free_mb]
, convert(bigint, round(((b.total_bytes / 1024.0)/1024.0),0)) as [total_mb]
, convert(bigint, round((((b.total_bytes - b.available_bytes) / 1024.0)/1024.0),0)) as [used_mb]
from sys.master_files as [a]
CROSS APPLY sys.dm_os_volume_stats(a.database_id, a.[file_id]) as [b]

-----------------------------------------

Here are the results:

DriveSpace

** One word of caution is that sys.dm_os_volume_stats will only produce stats for drives where a SQL Server database exists.

 

Another question: But… what if I am using SQL Server 2008 or older?

Another Answer: xp_fixeddrives

This extended stored procedure is not as useful regarding the information that it gives – only showing free space.  However, it DOES include all of the fixed drives on the server instead of just the drives where a SQL Server database exists.


------------------------------------

exec master.dbo.xp_fixeddrives

------------------------------------

Here are the results:

DriveSpace2

SQL Query to Get SQL Server CPU Utilization

For a quick and dirty way to get the CPU utilization from SQL Server you can use the sys.dm_os_ring_buffers DMV.

The DMV sys.dm_os_ring_buffers maintains approximately 1000 records, before wrapping around and replacing the oldest entries first. It exposes four columns but we’re primarily only interested in one of them, record, which contains XML data.

Here is a quick query to get CPU utilization from the SQL Server.


-------------------------------------------

select top 1
(convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') as [cpuIdle],
(convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') as [cpuSQLProcessUtilization],
100 - (convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') - (convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') as [CPUOtherProcessUtilization]

from sys.dm_os_ring_buffers a
where a.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
and a.record like '%<SystemHealth>%'
order by (convert(xml, record)).value('(./Record/@id)[1]', 'int') desc;

--------------------------------------------

Executing the above query will give you a result similar to the following:

CPUUtilization

One caveat about using this method though is that the ring buffer reports CPU usage once per minute.  This means that this is not necessarily “real time”, but will always be within 1 minute or less of the moment when the query was executed.

If you wanted to get the CPU usage for the last ten minutes you could take the above query and change the ‘top’ clause from a 1 to a 10.  Like so:


-------------------------------------------

select top 10
(convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') as [cpuIdle],
(convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') as [cpuSQLProcessUtilization],
100 - (convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') - (convert(xml, record)).value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') as [CPUOtherProcessUtilization]

from sys.dm_os_ring_buffers a
where a.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
and a.record like '%<SystemHealth>%'
order by (convert(xml, record)).value('(./Record/@id)[1]', 'int') desc;

--------------------------------------------

As you can see from the screen shot below we now receive the CPU usage statistics for the last 10 minutes.

CPUUtilization2

How do I find statements that are currently executing in SQL Server?

Question:

How do I find the statements that are executing in SQL Server?

Answer:

For such a task Microsoft has been kind enough to provide us with some pretty handy DMVs (Dynamic Management Views).
Below we will use the following DMVs and Table-valued functions:

  1. sys.dm_exec_requests – Returns information about each request that is executing within SQL Server.
  2. sys.dm_exec_sql_text – A table-valued function that returns the text of the SQL batch that is identified by the specified sql_handle. This table-valued function replaces the system function fn_get_sql. We can use this to get the text of the full batch as well as the specific currently executing statement within the batch.
  3. sys.dm_exec_query_plan – A table-valued function that returns the Showplan in XML format for the batch specified by the plan handle. The plan specified by the plan handle can either be cached or currently executing.
  4. sys.dm_exec_sessions – Returns one row per authenticated session on SQL Server. sys.dm_exec_sessions is a server-scope view that shows information about all active user connections and internal tasks.

Here is a query that I like to use to find the currently executing statements in SQL Server:

-----------------------------------------------------
select
  a.session_id
, a.blocking_session_id
, a.wait_type
, a.wait_resource
, a.wait_time
, a.start_time
, datediff(second, a.start_time, getdate()) as [duration_second]
, d.login_time
, d.last_request_start_time
, d.[status]
, d.login_name
, d.nt_domain
, d.nt_user_name
, d.[host_name]
, d.[program_name]

, DB_NAME(a.database_id) AS [database_name]
, OBJECT_SCHEMA_NAME(b.objectid, a.database_id) AS [schema_name]
, OBJECT_NAME(b.objectid, a.database_id) AS [object_name]

, b.[text] AS [FullSQLBatch]
, CASE a.statement_end_offset
			WHEN -1 THEN SUBSTRING(b.[text], (a.[statement_start_offset] / 2) + 1, 2147483647)
			WHEN 0 THEN b.[text]
			ELSE SUBSTRING(b.[text], (a.[statement_start_offset] / 2) + 1, (a.[statement_end_offset] - a.[statement_start_offset]) / 2)
			END AS [ActiveStatement]
, c.query_plan

, a.open_transaction_count
, a.open_resultset_count
, a.percent_complete
, a.reads
, a.writes
, a.logical_reads
, a.granted_query_memory

FROM sys.dm_exec_requests AS a
   OUTER APPLY sys.dm_exec_sql_text(a.[sql_handle]) AS b
   OUTER APPLY sys.dm_exec_query_plan(a.plan_handle) AS c
   LEFT JOIN sys.dm_exec_sessions AS d ON a.session_id = d.session_id
WHERE a.session_id >= 50
  and a.session_id <> @@spid
-----------------------------------------------------

 

Here is another iteration of the above statement. This one will also include information about the blocker (if there is one).

<pre>-----------------------------------------------------
select
  a.session_id
, a.blocking_session_id
, a.wait_type
, a.wait_resource
, a.wait_time
, a.start_time
, datediff(second, a.start_time, getdate()) as [duration_second]
, d.login_time
, d.last_request_start_time
, d.[status]
, d.login_name
, d.nt_domain
, d.nt_user_name
, d.[host_name]
, d.[program_name]

, DB_NAME(a.database_id) AS [database_name]
, OBJECT_SCHEMA_NAME(b.objectid, a.database_id) AS [schema_name]
, OBJECT_NAME(b.objectid, a.database_id) AS [object_name]

, b.[text] AS [FullSQLBatch]
, CASE a.statement_end_offset
			WHEN -1 THEN SUBSTRING(b.[text], (a.[statement_start_offset] / 2) + 1, 2147483647)
			WHEN 0 THEN b.[text]
			ELSE SUBSTRING(b.[text], (a.[statement_start_offset] / 2) + 1, (a.[statement_end_offset] - a.[statement_start_offset]) / 2)
			END AS [ActiveStatement]
, c.query_plan

, a.open_transaction_count
, a.open_resultset_count
, a.percent_complete
, a.reads
, a.writes
, a.logical_reads
, a.granted_query_memory

, '***** blocker info *****' as [***** blocker info *****]
, f.session_id as [blocker_session_id]
, f.blocking_session_id as [blocker_blocking_session_id]
, f.wait_type as [blocker_wait_type]
, f.wait_resource
, f.wait_time as [blocker_wait_time]
, f.start_time as [blocker_start_time]
, datediff(second, f.start_time, getdate()) as [blocker_duration_second]
, e.login_time as [blocker_login_time]
, e.last_request_start_time as [blocker_last_request_start_time]
, e.[status] as [blocker_status]
, e.login_name as [blocker_login_name]
, e.nt_domain as [blocker_nt_domain]
, e.nt_user_name as [blocker_nt_user_name]
, e.[host_name] as [blocker_host_name]
, e.[program_name] as [blocker_program_name]

, DB_NAME(f.database_id) AS [blocker_database_name]
, OBJECT_SCHEMA_NAME(h.objectid, f.database_id) AS [blocker_schema_name]
, OBJECT_NAME(h.objectid, f.database_id) AS [blocker_object_name]

, h.[text] AS [blocker_FullSQLBatch]
, CASE f.statement_end_offset
			WHEN -1 THEN SUBSTRING(h.[text], (f.[statement_start_offset] / 2) + 1, 2147483647)
			WHEN 0 THEN h.[text]
			ELSE SUBSTRING(h.[text], (f.[statement_start_offset] / 2) + 1, (f.[statement_end_offset] - f.[statement_start_offset]) / 2)
			END AS [blocker_ActiveStatement]
, g.query_plan as [blocker_query_plan]

, f.open_transaction_count as [blocker_open_transaction_count]
, f.open_resultset_count as [blocker_open_resultset_count]
, f.percent_complete as [blocker_percent_complete]
, f.reads as [blocker_reads]
, f.writes as [blocker_writes]
, f.logical_reads as [blocker_logical_reads]
, f.granted_query_memory as [blocker_granted_query_memory]

FROM sys.dm_exec_requests AS a
    OUTER APPLY sys.dm_exec_sql_text(a.[sql_handle]) AS b
    OUTER APPLY sys.dm_exec_query_plan(a.plan_handle) AS c
    LEFT JOIN sys.dm_exec_sessions AS d ON a.session_id = d.session_id

    left outer join sys.dm_exec_sessions as [e] on e.session_id = a.blocking_session_id
    left outer join sys.dm_exec_requests as [f] on f.session_id = a.blocking_session_id
    outer apply sys.dm_exec_query_plan(f.plan_handle) as [g]
    outer apply sys.dm_exec_sql_text(f.[sql_handle]) as [h]
WHERE a.session_id >= 50
  and a.session_id <> @@spid
-----------------------------------------------------

 

Three simple ways to use a SQL query to return the text of your stored procedure, function, view, etc.

Below are three simple ways to use a SQL query to get the text of a stored procedure, trigger, function, etc.

Method #1: SYS.SQL_MODULES

sys.sql_modules is a system DMV (Dynamic Management View) that returns a row for each object that is an SQL language-defined module in SQL Server. Objects of type P, RF, V, TR, FN, IF, TF, and R have an associated SQL module.

Here is an example:

-----------------------------------
select a.[definition] from sys.sql_modules as [a]
 join sys.objects as [b] on b.[object_id] = a.[object_id]
where b.name = 'YourStoredProcedureName';
-----------------------------------

 

Method #2: sp_helptext

sp_helptext is a built-in stored procedure that displays the definition of a user-defined rule, default, unencrypted Transact-SQL stored procedure, user-defined Transact-SQL function, trigger, computed column, CHECK constraint, view, or system object such as a system stored procedure.

Here is an example:

-----------------------------------
exec sp_helptext @objname = 'YourStoredProcedureName';
-----------------------------------

 

Method #3: sys.syscomments

sys.syscomments contains entries for each view, rule, default, trigger, CHECK constraint, DEFAULT constraint, and stored procedure within the database. The text column contains the original SQL definition statements.

Here is an example:

-----------------------------------
select a.[text] from sys.syscomments as [a]
 join sys.sysobjects as [b] on b.id = a.id
where b.name = 'YourStoredProcedureName';
-----------------------------------