Today's story is about me making assumptions on how things work based on the method's name. As the blog post says, I want to focus on two similar methods – GetTempFileName() and GetRandomFileName(), when using PowerShell. Still, since those methods are .NET based, it applies to a whole range of other languages – C#, F#, VisualBasic, and all others that I've never used.
First, let's see how you can use it in PowerShell and what's the output
[System.IO.Path]::GetRandomFileName()
[System.IO.Path]::GetTempFileName()
The difference is that one returns just the file name, the other returns the full path to a temp file. Isn't that great? So when I want to have a random name for a file to store in the temp folder I would use GetTempFileName(), and if I want to have just a random name I would use GetRandomFileName(). This was very useful for my PowerShell HTML module where I would need a temporary path to save the file if the user wouldn't provide any FilePath. I would then use:
[System.IO.Path]::GetTempFileName().Replace(".tmp",".html")
Super easy, functional, and working great for the last few years I've used it.
This brings me to today, where for the last two months, I've been blaming my Exchange team for changing something that influenced my PowerShell module PasswordSolution, sending emails impacting the runtime of the script. You see, PasswordSolution sends an email notification that the user's password expires. It uses Mailozaurr (MailKit/MimeKit) to send emails and PSWriteHTML to create an email body. To send 600-1000 emails daily on a domain of 60000 users would take more or less one hour, then suddenly it changed, and from 1 second to send an email, it took 40-60 seconds to send one email over 16 hours. As I've not touched anything – I blamed the Exchange team! They must have changed something (tarpit comes to mind), they were for sure doing some server changes, or maybe it's the fault of Load Balancer delaying things. I even went as far as contacting the MailKit developer to help troubleshoot where the email delays happen or to improve performance. But whatever we did, nothing helped. Sure we could see some improvements when running tests using my local account, taking 1 second to send an email, but as soon as the script runs as Task Scheduler, it would send emails over 16 hours again.
Finally, it was time to start debugging thins on my own! That pesky Exchange team is no good! They can't even handle fixing their Exchange servers! And so I did – with one slight difference – this time, instead of testing using my local account, I've opened a PowerShell window using the MSA account used to run the script. I run the same testing script that I used on my standard Active Directory account, and suddenly instead of 1-5 seconds per email, it takes 40-60 seconds, and an error message is thrown that a temp name can't be generated. That's weird. Then it hits me – I go to the temp folder, type „dir” and see hundreds of thousands of 0-byte files called tmpXXXX.tmp going and going and going, and never finishing. GetTempFileName() returns a temporary file name and creates it on a disk. That means every time you run it, it checks if the file exists, and if it does, it generates a new name and again creates it on the temp folder.
That means for every email I sent or prepared for sending but wouldn't it would create an empty file. It was even worse because email body generation actually used [System.IO.Path]::GetTempFileName().Replace(„.tmp,” „.html”) a different name, so the TMP file would never be used, and even the HTML extension was never used because it was all done in memory, so it was doing it unnecessarily. You may be thinking – but why was it taking so long to generate that name?
So why was it taking so long? Whenever I created an email, my script would generate a temp file (instead of a temp string) that led to 1000-10000 temp files created per day, each adding a problem for GetTempFileName() to make next. After running for months without cleaning the temp folder, one of two things happened – I've reached the limit where the GetTempFileName() method would try and generate a unique file name that doesn't exist. It hit 1.8 million files (trying to find a unique one every time), and it stopped, or it has a hardcoded timeout of 1 minute (more likely). Either way – I didn't want to investigate it anymore, as I felt that I'd already achieved the top idiot award for today. The delete command took about 10 minutes to delete all the temp files. And what do you know? Once deleted, the emails started flying in 1 second again.
Moral of this story? Always read the documentation! Use GetTempFileName() only if you need this file created and with this name. Use GetRandomFileName() if you want a random file name, just a string. If you need a temporary path with a random file name use a combination of the GetTempPath() method and GetRandomFileName().
[io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).html")
While not as pretty, it gives you flexibility and doesn't create random files that may not be really what you need! The other takeaway is that you should run some kind of cleanup activity on your servers, as you never know when some dumbass will fill the temp folders with junk.