{"id":79646,"date":"2016-08-05T00:01:12","date_gmt":"2016-08-05T07:01:12","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/?p=79646"},"modified":"2019-02-18T09:10:29","modified_gmt":"2019-02-18T16:10:29","slug":"use-powershell-to-maintain-iis-logs","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/use-powershell-to-maintain-iis-logs\/","title":{"rendered":"Use PowerShell to maintain IIS logs"},"content":{"rendered":"<p><strong>Summary<\/strong>: Learn how to use PowerShell to maintain and work with IIS logs.<\/p>\n<p>Welcome back guest blogger, Terri Donahue. Here are <a href=\"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/tag\/terri-donahue\/\">Terri\u2019s previous blog posts<\/a>. Terri is a Senior Customer Support Engineer for Dynamicweb NA and a Visual Studio and Development Technologies MVP. Twitter: @terrid_dw<\/p>\n<p>Internet Information Services (IIS) log maintenance has been a thorn in the side of web administrators for a while\u2026basically since the first release of IIS. There isn\u2019t a built-in utility to handle file compression, archival of log files, or deletion of log files. Depending on a site\u2019s traffic, these logs can grow quite big very quickly and begin to cause disk space issues. Some available utilities compress and delete log files, but all utilities that I have seen and that also handle archival have been home grown. Not everyone has a business need for archiving log files.\u00a0In my experience, there have been times that historical logs were needed to track down when a specific issue started occurring. You would be surprised how long it sometimes takes for issues on a website to be reported for troubleshooting. I put together a script that uses functions for file compression, archival, and deletion. By modularizing the script, it is easy to select only the functions that you want to use rather than having to use comment blocks to disable parts of the script.<\/p>\n<p>There are a few things to decide before you implement this script.<\/p>\n<ol>\n<li>How many days of unzipped logs do you want to keep?<\/li>\n<li>How many days of non-archived zipped logs do you want to retain in the current folder?<\/li>\n<li>Where do you want to archive the older zipped logs \u2013 local or remote?<\/li>\n<li>How long do you want to retain the archived logs?<\/li>\n<li>Do you want to log what was done?<\/li>\n<\/ol>\n<p>For the purposes of this script, I chose the following answers:<\/p>\n<ol>\n<li>Up to seven<\/li>\n<li>Up to seven<\/li>\n<li>Local archive directory<\/li>\n<li>Six months<\/li>\n<li>Yes<\/li>\n<\/ol>\n<h2>Global variables<\/h2>\n<p>Because I created the script as a group of functions, I used global variables rather than assigning file locations for each piece of the script.<\/p>\n<p style=\"padding-left: 30px\"><code>#Setting Global variables<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$Global:logDir = 'c:\\\\wwwlogs\\\\' #Location of IIS logs\n$Global:archDir = 'c:\\wwwlogs-archive\\' #archive directory location\n$Global:logFile = 'c:\\temp\\maintain-iislogs.txt'\n$global:logTime = Get-Date -Format 'MM-dd-yyyy_hh-mm-ss'<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>Clear-Content $LogFile<\/code><\/p>\n<p>I intentionally used \\\\ in the log directory variable because\u00a0I am using it as the pattern for a -replace command in the archive portion of the script. This has to be done to escape the \\ that is needed to be retained.<\/p>\n<h2>Log compression<\/h2>\n<p>I began by working on the function to compress existing log files. The script requires <a target=\"_blank\" href=\"http:\/\/7-zip.org\/download.html\">7-Zip<\/a>. It could be modified to use standard Windows compression if you prefer. If you don\u2019t want to install 7-Zip on your server, you can simply copy 7z.exe and 7z.dll from another computer\u00a0to the computer\u00a0where you want to run the script. The value of the <em>$days<\/em> variable is a negative number because it determines how many days in the past you want to go. This variable is used with the <strong>Get-Date AddDays<\/strong> method. I also wanted to ensure that the zipped files maintained the original creation date rather than the date the file was zipped. I used the <strong>LastWriteTime<\/strong> method to set this value on the new zip file.<\/p>\n<p>Function Compress-Logs<\/p>\n<p><code>{<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>&lt;#### 7-Zip variable I got it from the below link<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>#### http:\/\/mats.gardstad.se\/matscodemix\/2009\/02\/05\/calling-7-zip-from-powershell\/<\/code><\/p>\n<p style=\"padding-left: 30px\"><code># Alias for 7-zip ##&gt;<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>if (-not (Test-Path -Path \"$env:ProgramFiles\\7-Zip\\7z.exe\"))\u00a0 #update path to point to the location of 7-zip<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>throw \"$env:ProgramFiles\\7-Zip\\7z.exe needed\"<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>Set-Alias -Name sz -Value \"$env:ProgramFiles\\7-Zip\\7z.exe\"<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$days = \u2018-6\u2019 #this will result in 7 days of non-zipped log files<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$logs = Get-ChildItem -Recurse -Path $logdir -Attributes !Directory -Filter *.log\u00a0 | Where-Object -FilterScript {<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$_.LastWriteTime -lt (Get-Date).AddDays($days)<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>foreach ($log in $logs)<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 60px\">$name = $log.name #gets the filename<\/p>\n<p style=\"padding-left: 60px\"><code>$directory = $log.DirectoryName #gets the directory name<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$LastWriteTime = $log.LastWriteTime #gets the lastwritetime of the file<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$zipfile = $name.Replace('.log','.7z') #creates the zipped filename<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>sz a -t7z \"$directory\\$zipfile\" \"$directory\\$name\" #runs 7-zip with the provided parameters \u2013 name and location of the zip file and the file to zip<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>if($LastExitCode -eq 0) #verifies the zip process was successful<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 90px\"><code>Get-ChildItem $directory -Filter $zipfile | % {$_.LastWriteTime = $LastWriteTime} #sets the LastWriteTime of the zip file to match the original log file<\/code><\/p>\n<p style=\"padding-left: 90px\"><code>Remove-Item -Path $directory\\$name #deletes the original log file<\/code><\/p>\n<p style=\"padding-left: 90px\"><code>$logtime + ': Created archive ' + $directory + '\\' + $zipfile + '. Deleted original logfile: ' + $name | Out-File $logfile -Encoding UTF8 -Append #writes logfile entry<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>}<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Zipped log archival<\/h2>\n<p>This function moves zipped logs that are older than the retention period from the original IIS logs folder location to the archive location that\u2019s set in the global variables.<\/p>\n<p>Function Archive-Logs<\/p>\n<p><code>{<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$archiveDays = '-13' #this will provide 7 days of zipped log files in the original directory - all others will be archived<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$logFolders = Get-ChildItem -Path $logdir -Attributes Directory #gets the folders in the log directory<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$zipLogs = Get-ChildItem -Recurse -Path $logdir -Attributes !Directory -Filter *.7z\u00a0 | Where-Object -FilterScript {<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$_.LastWriteTime -lt (Get-Date).AddDays($archiveDays)<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>} #gets the zipped logs<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>foreach ($logFolder in $logFolders)<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$folder = $logFolder.name #gets the directory the logfile is in<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$folder = $folder -replace $logdir, '' #removes the original log directory keeping on the child portion of the name .ie c:\\wwwlogs\\w3svc becomes w3svc \u2013 needed for the folder creation and file move portions of this function<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$targetDir = $archdir + $folder<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>if (!(Test-Path -Path $targetDir -PathType Container))\u00a0 #checks if the folder exists in the archive location<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 90px\"><code>New-Item -ItemType directory -Path $targetDir #creates folder if it doesn\u2019t exist<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>foreach ($ziplog in $zipLogs)<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>{<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$origZipDir = $ziplog.DirectoryName #gets the current folder name<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$fileName = $ziplog.Name #gets the current zipped log name<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$source = $origZipDir + '\\' + $fileName #builds the source data<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$destDir = $origZipDir -replace $logdir, '' #removes the parent log folder<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$destination = $archdir + $destDir + '\\' + $fileName #builds the destination data<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>Move-Item $source -Destination $destination #moves the file from the current location to the archive location<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$logtime + ': Moved archive ' + $source + ' to ' + $destination | Out-File $logfile -Encoding UTF8 -Append #creates logfile entry<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>}<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Zipped log deletion<\/h2>\n<p>And finally, the cleanup\/delete function. The retention period differs depending on the application. For example, PCI solutions require a year or more of archived logs to be retained. This value should correspond to your retention policy.<\/p>\n<p><code>Function Delete-Logs {<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$delMonths = '-6' #retains 6 months of logs - adjust to meet your company's retention plan<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>$delLogs = Get-ChildItem -Recurse -Path $archdir -Attributes !Directory -Filter *.7z\u00a0 | Where-Object -FilterScript {<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$_.LastWriteTime -lt (Get-Date).AddMonths($delMonths)<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>} #gets the list of logs older than specified for deletion<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>Foreach ($delLog in $delLogs) {<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$filename = $delLog.Name #gets the filename<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$delDir = $delLog.DirectoryName #gets the directory<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$delFile = $delDir+ '\\' + $filename #builds the delete data<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>Remove-Item $delFile #deletes the file<\/code><\/p>\n<p style=\"padding-left: 60px\"><code>$logtime + ': Deleted archive ' + $delfile | Out-File $logfile -Encoding UTF8 -Append<\/code><\/p>\n<p style=\"padding-left: 30px\"><code>} #creates the logfile entry<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Calling the functions<\/h2>\n<p>The final piece of the script is to call the functions. You can remark out any of the functions that you don\u2019t want to use.<\/p>\n<p>Compress-Logs<\/p>\n<p>Archive-Logs<\/p>\n<p>Delete-Logs<\/p>\n<p>The full script can be downloaded here on the <a target=\"_blank\" href=\"https:\/\/gallery.technet.microsoft.com\/scriptcenter\/Script-for-IIS-Logs-b8d881af\">Script Gallery<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<p>Thanks, Terri, for sharing your time and knowledge.<\/p>\n<p>I invite you to follow me on <a target=\"_blank\" href=\"http:\/\/bit.ly\/scriptingguystwitter\">Twitter<\/a> and <a target=\"_blank\" href=\"http:\/\/bit.ly\/scriptingguysfacebook\">Facebook<\/a>. If you have any questions, send email to me at <a target=\"_blank\" href=\"mailto:scripter@microsoft.com\">scripter@microsoft.com<\/a>, or post your questions on the <a target=\"_blank\" href=\"http:\/\/bit.ly\/scriptingforum\">Official Scripting Guys Forum<\/a>. Also check out my <a target=\"_blank\" href=\"https:\/\/blogs.technet.microsoft.com\/msoms\/\">Microsoft Operations Management Suite Blog<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson\n<\/strong>Microsoft Scripting Guy<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Learn how to use PowerShell to maintain and work with IIS logs. Welcome back guest blogger, Terri Donahue. Here are Terri\u2019s previous blog posts. Terri is a Senior Customer Support Engineer for Dynamicweb NA and a Visual Studio and Development Technologies MVP. Twitter: @terrid_dw Internet Information Services (IIS) log maintenance has been a thorn [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[568,641],"tags":[56,326,695,3,546,45],"class_list":["post-79646","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hey-scripting-guy","category-windows-powershell","tag-guest-blogger","tag-iis","tag-logs-and-logging","tag-scripting-guy","tag-terri-donahue","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Learn how to use PowerShell to maintain and work with IIS logs. Welcome back guest blogger, Terri Donahue. Here are Terri\u2019s previous blog posts. Terri is a Senior Customer Support Engineer for Dynamicweb NA and a Visual Studio and Development Technologies MVP. Twitter: @terrid_dw Internet Information Services (IIS) log maintenance has been a thorn [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/79646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=79646"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/79646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=79646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=79646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=79646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}