Shrink SQL Log Files
If you’re a Windows Administrator and have Microsoft SQL Servers in your environment–and you’d be a rare bird if you didn’t–then you’ve encountered this problem. Backups are missed for awhile, massive restore is done, something happens and your transaction log file grows way too big. It’s not uncommon to find it being much larger then the actual database! If you’re like me you would RDP to the server, check out the data drive and see that the log file itself was out of size. You’d then hit Google to find out how to do a shrink file, then have open SQL Management Studio and try to figure out the logical name of the log file, then run the query. Usually about 20 minutes of lookups, all so you can run a SQL query that typically takes a few seconds to complete. Time to address this using PowerShell.
Before going any further, I want to let you know that I heavily leverage my Invoke-SQLQuery function to accomplish this. I’ve actually built a module at work that includes this function so I can run it at any time–and the Shrink SQL Log function is now in the same module!
So let’s outline what we want this script to accomplish:
- Point the script at a SQL server
- List the databases, database and log sizes. We’ll also need the last backup date because you can’t really shrink the log until you have a good backup
- Graphically display this information, letting the user select which databases they want to shrink (Out-GridView)
- Shrink the database
- Report the before and after information
Once you break it down you quickly realize that this is a pretty simple script, but we will need to display all that information, which should be stored in SQL tables somewhere. How hard could it be? Well, as it turns out, pretty hard. I’m sure I’ve mentioned this before, but I’m not a SQL DBA and I don’t even play one on TV and while I’m much better at writing queries then I was a few months ago I’m FAR FAR from an expert. So I broke out my Google-fu to discover how to display databases, their file and log file sizes.
Now I have the database sizes, I needed to include the last backup too. I found the SQL code to do it from Pinal Dave at SQLAuthority.com. I have to be honest here. I tried for a couple of hours to get these two mashed together and have one query to rule them all. I was not successful at this and ended up running both queries and then manually bringing them together in PowerShell. After I got that working, I finished the whole script with all the functionality I wanted working. But I was bothered by the 2 query system and I later came back to it and did eventually come up with a single query that pulled all the information on one fell sweep! Here’s Pinal’s original code:
And here’s the final query I came up:
I like to keep my SQL commands all upper case because it’s a great way of differentiating between SQL command and field/database names. I also added CAST and ROUND in there to format the data just the way I wanted it. Ultimately the data is being displayed to the user, and going out to 4 decimal places just isn’t user friendly so I took it down to 2 decimal places (surprisingly difficult in SQL).
Next comes 2 lines of code, which in essence is the very core of this script:
First one runs the query above and returns all the information that I need. I need to the database_id because at this point I still don’t know the logical name of the transaction log file, and I will have to discover that after the user has selected the databases they want to shrink. And once I know that I can start a loop on the Selected databases and begin the shrinkage process. Yes, I said it.
This is the point where I mention a little scope creep in the project. I’ve been talking about how this script is all about shrinking the log file, and that’s even what I ended up calling the script, but I realized it didn’t have to be just that. On very rare occasions you’ll want to shrink the database file too. Now, you could just go into SSMS and do that from the GUI and considering the times you’re going to be called to do this, doing it via he GUI is perfectly fine. But I’ve already done about 95% of the work so why not include it in this script? That’s where $Type comes in. In SQL the type of the database is 0 if it’s the database and 1 if it’s the transaction log. But if I specify a $Database switch, it’s going to be 1 when I want to do the Database and 0 when I want to do the transaction logs. Might as well just use this switch as my key, so I flip the boolean value to match how SQL defines type and then cast the boolean as an integer which then turns it from $true/$false to 1/0. Now the $Type variable will match the type field in SQL and all I have to do is include it in the query to get the logical name ($Name = … above) and I’ve got the proper file. I then use another Invoke-SQLQuery call and use the DBCC SHRINKFILE command to drop the file size to the smallest limit that SQL will allow.
After that I run another file size query–pretty much Alex’s code–with a WHERE clause to limit it to just the database I’m working with. From there I can build the before/after output:
My original object $Select is still valid and shows the databases that were selected for shrinkage, so I just use Add-Member to add to that object and add the after information so you can see how the log file has shrunk. And that’s it. The rest of the script is comment-based help and error trapping.
You may have noticed I used a non-standard verb here. Typically I avoid this as the verbs approved by Microsoft are usually fine, but in this case it just wasn’t. There’s a Expand verb, but no Shrink?! And Shrink is just going to be too well known, especially to SQL DBA’s that I just felt no choice in using it. So if you ever load it into a module, you’ll need to use the -DisableNameChecking switch to suppress the warning.