├── Archive ├── CopyMMS2015Files.ps1 ├── CopyMMS2016Files.ps1 ├── CopyMMS2017Files.ps1 ├── CopyMMS2018Files.ps1 ├── CopyMMS2019Files.ps1 └── CopyMMSDE2018Files.ps1 ├── Get-MMSSessionContent.ps1 └── README.md /Archive/CopyMMS2015Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: CopyMMS2015Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # # 9 | ############################################## 10 | 11 | $baseLocation = 'C:\Conferences\MMS' 12 | Clear-Host 13 | $MMSDates='http://mms2015.sched.org/2015-11-08','http://mms2015.sched.org/2015-11-09','http://mms2015.sched.org/2015-11-10','http://mms2015.sched.org/2015-11-11' 14 | ForEach ($Date in $MMSDates) 15 | { 16 | $uri = $Date 17 | $sched = Invoke-WebRequest -Uri $uri -WebSession $mms 18 | $links = $sched.Links 19 | $links | ForEach-Object { 20 | if(($PSItem.href -like '*event/*') -and ($PSItem.innerText -notlike '*birds*')) 21 | { 22 | $eventUrl = $PSItem.href 23 | $eventTitle = $($PSItem.innerText -replace "full$", "") -replace "filling$", "" 24 | "Checking session '{0}' for downloads" -f $eventTitle 25 | $eventTitle = $eventTitle -replace "\W+", "_" 26 | 27 | $uri = 'http://mms2015.sched.org' 28 | $event = Invoke-WebRequest -Uri $($uri + $eventUrl) 29 | $eventLinks = $event.Links 30 | 31 | $eventLinks | ForEach-Object { 32 | $eventFileUrl = $PSItem.href;$filename = $PSItem.href;if($eventFileUrl -like '*hosted_files*') 33 | { 34 | $downloadPath = $baseLocation + '\mms2015\' + $eventTitle 35 | $filename = $filename.substring(39) 36 | $outputFilePath = $downloadPath + '\' + $filename 37 | if((Test-Path -Path $($downloadPath)) -eq $false){New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null} 38 | if((Test-Path -Path $outputFilePath) -eq $false) 39 | { 40 | "...attempting to download '{0}'" -f $filename 41 | Invoke-WebRequest -Uri $eventFileUrl -OutFile $outputfilepath -WebSession $mms;$doDownload=$false; 42 | Unblock-File $outputFilePath 43 | $stopit = $true 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Archive/CopyMMS2016Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: CopyMMS2016Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # # 9 | ############################################## 10 | 11 | $baseLocation = 'C:\Conferences\MMS' 12 | Clear-Host 13 | $MMSDates='http://mms2016.sched.org/2016-05-16', 'http://mms2016.sched.org/2016-05-17', 'http://mms2016.sched.org/2016-05-18','http://mms2016.sched.org/2016-05-19' 14 | ForEach ($Date in $MMSDates) 15 | { 16 | $uri = $Date 17 | $sched = Invoke-WebRequest -Uri $uri -WebSession $mms 18 | $links = $sched.Links 19 | $links | ForEach-Object { 20 | if(($PSItem.href -like '*event/*') -and ($PSItem.innerText -notlike '*birds*')) 21 | { 22 | $eventUrl = $PSItem.href 23 | $eventTitle = $($PSItem.innerText -replace "full$", "") -replace "filling$", "" 24 | "Checking session '{0}' for downloads" -f $eventTitle 25 | $eventTitle = $eventTitle -replace "\W+", "_" 26 | 27 | $uri = 'http://mms2016.sched.org' 28 | $event = Invoke-WebRequest -Uri $($uri + $eventUrl) 29 | $eventLinks = $event.Links 30 | 31 | $eventLinks | ForEach-Object { 32 | $eventFileUrl = $PSItem.href;$filename = $PSItem.href;if($eventFileUrl -like '*hosted_files*') 33 | { 34 | $downloadPath = $baseLocation + '\mms2016\' + $eventTitle 35 | $filename = $filename.substring(39) 36 | $filename = $filename.Replace('%20',' ') 37 | $outputFilePath = $downloadPath + '\' + $filename 38 | if((Test-Path -Path $($downloadPath)) -eq $false){New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null} 39 | if((Test-Path -Path $outputFilePath) -eq $false) 40 | { 41 | "...attempting to download '{0}'" -f $filename 42 | Invoke-WebRequest -Uri $eventFileUrl -OutFile $outputfilepath -WebSession $mms;$doDownload=$false; 43 | Unblock-File $outputFilePath 44 | $stopit = $true 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Archive/CopyMMS2017Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: Copymms2017Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # Evan Yeung # 9 | # http://www.forevanyeung.com # 10 | # # 11 | ############################################## 12 | 13 | $baseLocation = 'C:\Conferences\MMS' 14 | Clear-Host 15 | $MMSDates='2017-05-14', '2017-05-15', '2017-05-16','2017-05-17','2017-05-18' 16 | $web = Invoke-WebRequest 'https://mms2017.sched.com/login' -WebSession $mms -Method POST -Body $form.Fields 17 | 18 | ForEach ($Date in $MMSDates) { 19 | "Checking day '{0}' for downloads" -f $Date 20 | 21 | $sched = Invoke-WebRequest -Uri $("https://mms2017.sched.org/" + $Date + "/list/descriptions") -WebSession $mms 22 | $links = $sched.Links 23 | 24 | $eventsIndex = @() 25 | $links | ForEach-Object { if(($_.href -like "*/event/*") -and ($_.innerText -notlike "here")) { 26 | $eventsIndex += (, ($links.IndexOf($_), $_.innerText)) 27 | } } 28 | 29 | $i = 0 30 | While($i -lt $eventsIndex.Count) { 31 | $eventTitle = $eventsIndex[$i][1] 32 | $eventTitle = $eventTitle.Trim() 33 | $eventTitle = $eventTitle -replace "\W+", "_" 34 | 35 | $links[$eventsIndex[$i][0]..$(if($i -eq $eventsIndex.Count - 1) {$links.Count-1} else {$eventsIndex[$i+1][0]})] | ForEach-Object { 36 | if($_.href -like "*hosted_files*") { 37 | $downloadPath = $baseLocation + '\mms2017\' + $Date + '\' + $eventTitle 38 | $filename = $_.href 39 | $filename = $filename.substring(39) 40 | $filename = $filename.Replace('%20',' ') 41 | $outputFilePath = $downloadPath + '\' + $filename 42 | if((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 43 | if((Test-Path -Path $outputFilePath) -eq $false) 44 | { 45 | "...attempting to download '{0}'" -f $filename 46 | Invoke-WebRequest -Uri $_.href -OutFile $outputfilepath -WebSession $mms 47 | Unblock-File $outputFilePath 48 | $stopit = $true 49 | } 50 | } 51 | } 52 | 53 | $i++ 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Archive/CopyMMS2018Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: Copymms2018Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # Evan Yeung # 9 | # http://www.forevanyeung.com # 10 | # Chris Kibble # 11 | # http://www.christopherkibble.com # 12 | # Jon Warnken # 13 | # http://www.mrbodean.net # 14 | # # 15 | ############################################## 16 | 17 | $baseLocation = 'C:\Conferences\MMS' 18 | Clear-Host 19 | Add-Type -AssemblyName System.Web 20 | $web = Invoke-WebRequest 'https://mms2018.sched.com/login' -SessionVariable mms 21 | $c = $host.UI.PromptForCredential('Sched Credentials', 'Enter Credentials', '', '') 22 | $form = $web.Forms[1] 23 | $form.fields['username'] = $c.UserName 24 | $form.fields['password'] = $c.GetNetworkCredential().Password 25 | "Logging in..." 26 | 27 | $mmsHome = Invoke-WebRequest 'https://mms2018.sched.org/' -WebSession $mms 28 | 29 | $htmlDate = $mmsHome.ParsedHtml.IHTMLDocument3_GetElementById('sched-sidebar-filters-dates') 30 | $htmlPopoverBody = $htmlDate.getElementsByClassName('popover') 31 | $htmlDateList = $htmlPopoverBody[0].getElementsByTagName('li') 32 | 33 | $MMSDates = @() 34 | $htmlDateList | ForEach-Object { 35 | out-null -InputObject $($_.innerHTML -match "\d{4}-\d{2}-\d{2}") 36 | $MMSDates += $matches[0] 37 | } 38 | 39 | $web = Invoke-WebRequest 'https://mms2018.sched.com/login' -WebSession $mms -Method POST -Body $form.Fields 40 | if(-Not ($web.InputFields.FindByName("login"))) { 41 | ForEach ($Date in $MMSDates) { 42 | "Checking day '{0}' for downloads" -f $Date 43 | 44 | $sched = Invoke-WebRequest -Uri $("https://mms2018.sched.org/" + $Date + "/list/descriptions") -WebSession $mms 45 | $links = $sched.Links 46 | 47 | $eventsIndex = @() 48 | $links | ForEach-Object { if(($_.href -like "*/event/*") -and ($_.innerText -notlike "here")) { 49 | $eventsIndex += (, ($links.IndexOf($_), $_.innerText)) 50 | } } 51 | 52 | $i = 0 53 | While($i -lt $eventsIndex.Count) { 54 | $eventTitle = $eventsIndex[$i][1] 55 | $eventTitle = $eventTitle -replace "[^A-Za-z0-9-_. ]", "" 56 | $eventTitle = $eventTitle.Trim() 57 | $eventTitle = $eventTitle -replace "\W+", "_" 58 | 59 | $links[$eventsIndex[$i][0]..$(if($i -eq $eventsIndex.Count - 1) {$links.Count-1} else {$eventsIndex[$i+1][0]})] | ForEach-Object { 60 | if($_.href -like "*hosted_files*") { 61 | $downloadPath = $baseLocation + '\mms2018\' + $Date + '\' + $eventTitle 62 | $filename = $_.href 63 | $filename = $filename.substring(40) 64 | 65 | # Replace HTTP Encoding Characters (e.g. %20) with the proper equivalent. 66 | $filename = [System.Web.HttpUtility]::UrlDecode($filename) 67 | 68 | # Replace non-standard characters 69 | $filename = $filename -replace "[^A-Za-z0-9\.\-_ ]", "" 70 | 71 | $outputFilePath = $downloadPath + '\' + $filename 72 | 73 | # Reduce Total Path to 255 characters. 74 | $outputFilePathLen = $outputFilePath.Length 75 | 76 | If($outputFilePathLen -ge 255) { 77 | $fileExt = [System.IO.Path]::GetExtension($outputFilePath) 78 | $newFileName = $outputFilePath.Substring(0,$($outputFilePathLen - $fileExt.Length)) 79 | $newFileName = $newFileName.Substring(0, $(255 - $fileExt.Length)).trim() 80 | $newFileName = "$newFileName$fileExt" 81 | $outputFilePath = $newFileName 82 | } 83 | 84 | if((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 85 | if((Test-Path -Path $outputFilePath) -eq $false) 86 | { 87 | "...attempting to download '{0}'" -f $filename 88 | Invoke-WebRequest -Uri $_.href -OutFile $outputfilepath -WebSession $mms 89 | Unblock-File $outputFilePath 90 | } 91 | } 92 | } 93 | 94 | $i++ 95 | } 96 | } 97 | } else { 98 | "Login failed. Exiting script." 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Archive/CopyMMS2019Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: CopyMMSDE2018Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # Evan Yeung # 9 | # http://www.forevanyeung.com # 10 | # Chris Kibble # 11 | # http://www.christopherkibble.com # 12 | # Jon Warnken # 13 | # http://www.mrbodean.net # 14 | # Oliver Baddeley Edited For # 15 | # Desert Edition # 16 | ############################################## 17 | 18 | $baseLocation = 'C:\Temp\Conferences\MMS' 19 | Clear-Host 20 | Add-Type -AssemblyName System.Web 21 | $web = Invoke-WebRequest 'https://mms2019.sched.com/login' -SessionVariable mms 22 | $c = $host.UI.PromptForCredential('Sched Credentials', 'Enter Credentials', '', '') 23 | $form = $web.Forms[1] 24 | $form.fields['username'] = $c.UserName 25 | $form.fields['password'] = $c.GetNetworkCredential().Password 26 | "Logging in..." 27 | 28 | $mmsHome = Invoke-WebRequest 'https://mms2019.sched.com/' -WebSession $mms 29 | 30 | $htmlDate = $mmsHome.ParsedHtml.IHTMLDocument3_GetElementById('sched-sidebar-filters-dates') 31 | $htmlPopoverBody = $htmlDate.getElementsByClassName('popover') 32 | $htmlDateList = $htmlPopoverBody[0].getElementsByTagName('li') 33 | 34 | $MMSDates = @() 35 | $htmlDateList | ForEach-Object { 36 | out-null -InputObject $($_.innerHTML -match "\d{4}-\d{2}-\d{2}") 37 | $MMSDates += $matches[0] 38 | } 39 | 40 | $web = Invoke-WebRequest 'https://mms2019.sched.com/login' -WebSession $mms -Method POST -Body $form.Fields 41 | if(-Not ($web.InputFields.FindByName("login"))) { 42 | ForEach ($Date in $MMSDates) { 43 | "Checking day '{0}' for downloads" -f $Date 44 | 45 | $sched = Invoke-WebRequest -Uri $("https://mms2019.sched.org/" + $Date + "/list/descriptions") -WebSession $mms 46 | $links = $sched.Links 47 | 48 | $eventsIndex = @() 49 | $links | ForEach-Object { if(($_.href -like "*/event/*") -and ($_.innerText -notlike "here")) { 50 | $eventsIndex += (, ($links.IndexOf($_), $_.innerText)) 51 | } } 52 | 53 | $i = 0 54 | While($i -lt $eventsIndex.Count) { 55 | $eventTitle = $eventsIndex[$i][1] 56 | $eventTitle = $eventTitle -replace "[^A-Za-z0-9-_. ]", "" 57 | $eventTitle = $eventTitle.Trim() 58 | $eventTitle = $eventTitle -replace "\W+", "_" 59 | 60 | $links[$eventsIndex[$i][0]..$(if($i -eq $eventsIndex.Count - 1) {$links.Count-1} else {$eventsIndex[$i+1][0]})] | ForEach-Object { 61 | if($_.href -like "*hosted_files*") { 62 | $downloadPath = $baseLocation + '\mms2019\' + $Date + '\' + $eventTitle 63 | $filename = $_.href 64 | $filename = $filename.substring(40) 65 | 66 | # Replace HTTP Encoding Characters (e.g. %20) with the proper equivalent. 67 | $filename = [System.Web.HttpUtility]::UrlDecode($filename) 68 | 69 | # Replace non-standard characters 70 | $filename = $filename -replace "[^A-Za-z0-9\.\-_ ]", "" 71 | 72 | # Remove 7 characters added to filenames in 2019 73 | $filename = $filename.Substring(7) 74 | 75 | $outputFilePath = $downloadPath + '\' + $filename 76 | 77 | # Reduce Total Path to 255 characters. 78 | $outputFilePathLen = $outputFilePath.Length 79 | 80 | If($outputFilePathLen -ge 255) { 81 | $fileExt = [System.IO.Path]::GetExtension($outputFilePath) 82 | $newFileName = $outputFilePath.Substring(0,$($outputFilePathLen - $fileExt.Length)) 83 | $newFileName = $newFileName.Substring(0, $(255 - $fileExt.Length)).trim() 84 | $newFileName = "$newFileName$fileExt" 85 | $outputFilePath = $newFileName 86 | } 87 | 88 | if((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 89 | if((Test-Path -Path $outputFilePath) -eq $false) 90 | { 91 | "...attempting to download '{0}'" -f $filename 92 | Invoke-WebRequest -Uri $_.href -OutFile $outputfilepath -WebSession $mms 93 | Unblock-File $outputFilePath 94 | } 95 | } 96 | } 97 | 98 | $i++ 99 | } 100 | } 101 | } else { 102 | "Login failed. Exiting script." 103 | } 104 | 105 | -------------------------------------------------------------------------------- /Archive/CopyMMSDE2018Files.ps1: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # # 3 | # File: Copymms2018Files.ps1 # 4 | # Author: Duncan Russell # 5 | # http://www.sysadmintechnotes.com # 6 | # Edited: Andrew Johnson # 7 | # http://www.andrewj.net # 8 | # Evan Yeung # 9 | # http://www.forevanyeung.com # 10 | # Chris Kibble # 11 | # http://www.christopherkibble.com # 12 | # Jon Warnken # 13 | # http://www.mrbodean.net # 14 | # Oliver Baddeley Edited For # 15 | # Desert Edition # 16 | ############################################## 17 | 18 | $baseLocation = 'C:\Temp\Conferences\MMS' 19 | Clear-Host 20 | Add-Type -AssemblyName System.Web 21 | $web = Invoke-WebRequest 'https://mmsde2018.sched.com/login' -SessionVariable mms 22 | $c = $host.UI.PromptForCredential('Sched Credentials', 'Enter Credentials', '', '') 23 | $form = $web.Forms[1] 24 | $form.fields['username'] = $c.UserName 25 | $form.fields['password'] = $c.GetNetworkCredential().Password 26 | "Logging in..." 27 | 28 | $mmsHome = Invoke-WebRequest 'https://mmsde2018.sched.com/' -WebSession $mms 29 | 30 | $htmlDate = $mmsHome.ParsedHtml.IHTMLDocument3_GetElementById('sched-sidebar-filters-dates') 31 | $htmlPopoverBody = $htmlDate.getElementsByClassName('popover') 32 | $htmlDateList = $htmlPopoverBody[0].getElementsByTagName('li') 33 | 34 | $MMSDates = @() 35 | $htmlDateList | ForEach-Object { 36 | out-null -InputObject $($_.innerHTML -match "\d{4}-\d{2}-\d{2}") 37 | $MMSDates += $matches[0] 38 | } 39 | 40 | $web = Invoke-WebRequest 'https://mmsde2018.sched.com/login' -WebSession $mms -Method POST -Body $form.Fields 41 | if(-Not ($web.InputFields.FindByName("login"))) { 42 | ForEach ($Date in $MMSDates) { 43 | "Checking day '{0}' for downloads" -f $Date 44 | 45 | $sched = Invoke-WebRequest -Uri $("https://mmsde2018.sched.org/" + $Date + "/list/descriptions") -WebSession $mms 46 | $links = $sched.Links 47 | 48 | $eventsIndex = @() 49 | $links | ForEach-Object { if(($_.href -like "*/event/*") -and ($_.innerText -notlike "here")) { 50 | $eventsIndex += (, ($links.IndexOf($_), $_.innerText)) 51 | } } 52 | 53 | $i = 0 54 | While($i -lt $eventsIndex.Count) { 55 | $eventTitle = $eventsIndex[$i][1] 56 | $eventTitle = $eventTitle -replace "[^A-Za-z0-9-_. ]", "" 57 | $eventTitle = $eventTitle.Trim() 58 | $eventTitle = $eventTitle -replace "\W+", "_" 59 | 60 | $links[$eventsIndex[$i][0]..$(if($i -eq $eventsIndex.Count - 1) {$links.Count-1} else {$eventsIndex[$i+1][0]})] | ForEach-Object { 61 | if($_.href -like "*hosted_files*") { 62 | $downloadPath = $baseLocation + '\mms2018\' + $Date + '\' + $eventTitle 63 | $filename = $_.href 64 | $filename = $filename.substring(40) 65 | 66 | # Replace HTTP Encoding Characters (e.g. %20) with the proper equivalent. 67 | $filename = [System.Web.HttpUtility]::UrlDecode($filename) 68 | 69 | # Replace non-standard characters 70 | $filename = $filename -replace "[^A-Za-z0-9\.\-_ ]", "" 71 | 72 | $outputFilePath = $downloadPath + '\' + $filename 73 | 74 | # Reduce Total Path to 255 characters. 75 | $outputFilePathLen = $outputFilePath.Length 76 | 77 | If($outputFilePathLen -ge 255) { 78 | $fileExt = [System.IO.Path]::GetExtension($outputFilePath) 79 | $newFileName = $outputFilePath.Substring(0,$($outputFilePathLen - $fileExt.Length)) 80 | $newFileName = $newFileName.Substring(0, $(255 - $fileExt.Length)).trim() 81 | $newFileName = "$newFileName$fileExt" 82 | $outputFilePath = $newFileName 83 | } 84 | 85 | if((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 86 | if((Test-Path -Path $outputFilePath) -eq $false) 87 | { 88 | "...attempting to download '{0}'" -f $filename 89 | Invoke-WebRequest -Uri $_.href -OutFile $outputfilepath -WebSession $mms 90 | Unblock-File $outputFilePath 91 | } 92 | } 93 | } 94 | 95 | $i++ 96 | } 97 | } 98 | } else { 99 | "Login failed. Exiting script." 100 | } 101 | 102 | -------------------------------------------------------------------------------- /Get-MMSSessionContent.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Gathers and downloads files from Midwest Management Summit conference sessions 4 | .DESCRIPTION 5 | This script gathers and downloads files from Midwest Management Summit conference sessions. You must 6 | have a valid login to Sched for the year you're attempting to download. 7 | .INPUTS 8 | None 9 | .OUTPUTS 10 | All session content from the specified years. 11 | .NOTES 12 | Version: 1.7.1 13 | Author: Andrew Johnson 14 | Modified Date: 5/12/2025 15 | Purpose/Change: Fixes a bug for Windows PowerShell 16 | 17 | Original author (2015 script): Duncan Russell - http://www.sysadmintechnotes.com 18 | Edits made by: 19 | Evan Yeung - https://www.forevanyeung.com 20 | Chris Kibble - https://www.christopherkibble.com 21 | Jon Warnken - https://www.mrbodean.net 22 | Oliver Baddeley - Edited for Desert Edition 23 | Benjamin Reynolds - https://sqlbenjamin.wordpress.com/ 24 | Jorge Suarez - https://github.com/jorgeasaurus 25 | Nathan Ziehnert - https://z-nerd.com 26 | Piotr Gardy - https://garit.pro 27 | 28 | 29 | TODO: 30 | [ ] Create a version history in these notes? Something like this: 31 | Version History/Notes: 32 | Date Version Author Notes 33 | ??/??/2015 1.0 Duncan Russell Initial Creation? 34 | 11/13/2019 1.1 Andrew Johnson Added logic to only authenticate if content for the specified sessions has not been made public 35 | 11/02/2021 1.2 Benjamin Reynolds Added SingleEvent, MultipleEvent, and AllEvent parameters/logic; simplified logic; added a Session Info 36 | text file containing details of the event 37 | 04/05/2023 1.3 Jorge Suarez Modified login body string for downloading session content 38 | 11/06/2023 1.4 Nathan Ziehnert Adds support for PowerShell 7.x, revamps the webscraping bit to be cross platform (no html parser in core). 39 | Sets default directory for non-Microsoft OS to be $HOME\Downloads\MMSContent. Ugly basic HTML parser for the 40 | session info file, but it should suffice for now. 41 | 04/28/2024 1.5 Andrew Johnson Updated and tested to include 2024 at MOA 42 | 10/20/2024 1.6 Andrew Johnson Updated and tested to include MMS Flamingo Edition 43 | 10/26/2024 1.6.1 Piotr Gardy Adds functionality to re-download and check if file was updated on server 44 | 5/1/2025 1.7 Andrew Johnson Updated and tested to include 2025 at MOA 45 | 5/12/2025 1.7.1 Nathan Ziehnert Fixes a bug where the script hangs on Windows PowerShell on logon for some users 46 | Fixes the regex for the session descriptions and speakers (unknown when this broke) 47 | Adds throttling to avoid 429 Too Many Requests errors (if request fails due to 429, script waits 20 seconds and retries) 48 | Could be improved with exponential backoff, but this is a start - also 20 seconds seemed to work best (15 almost worked) 49 | 50 | 51 | .EXAMPLE 52 | .\Get-MMSSessionContent.ps1 -ConferenceList @('2025atmoa','2024fll'); 53 | 54 | Downloads all MMS session content from 2025 at MOA and 2024 Flamingo Edition to C:\Conferences\MMS\ 55 | 56 | .EXAMPLE 57 | .\Get-MMSSessionContent.ps1 -DownloadLocation "C:\Temp\MMS" -ConferenceId 2025atmoa 58 | 59 | Downloads all MMS session content from 2025 at MOA to C:\Temp\MMS\ 60 | 61 | .EXAMPLE 62 | .\Get-MMSSessionContent.ps1 -All 63 | 64 | Downloads all MMS session content from all years to C:\Conferences\MMS\ 65 | 66 | .EXAMPLE 67 | .\Get-MMSSessionContent.ps1 -All -ExcludeSessionDetails; 68 | 69 | Downloads all MMS session content from all years to C:\Conferences\MMS\ BUT does not include a "Session Info.txt" file for each session containing the session details 70 | 71 | .LINK 72 | Project URL - https://github.com/AndrewJNet/CopyMMSFiles 73 | #> 74 | [cmdletbinding(PositionalBinding = $false)] 75 | Param( 76 | [Parameter(Mandatory = $false)][string]$DownloadLocation = "C:\Conferences\MMS", # could validate this: [ValidateScript({(Test-Path -Path (Split-Path $PSItem))})] 77 | [Parameter(Mandatory = $true, ParameterSetName = 'SingleEvent')] 78 | [ValidateSet("2015", "2016", "2017", "2018", "de2018", "2019", "jazz", "miami", "2022atmoa", "2023atmoa", "2023miami", "2024atmoa", "2024fll", "2025atmoa")] 79 | [string]$ConferenceId, 80 | [Parameter(Mandatory = $true, ParameterSetName = 'MultipleEvents', HelpMessage = "This needs to bwe a list or array of conference ids/years!")] 81 | [System.Collections.Generic.List[string]]$ConferenceList, 82 | [Parameter(Mandatory = $true, ParameterSetName = 'AllEvents')][switch]$All, 83 | [Parameter(Mandatory = $false)][switch]$ExcludeSessionDetails, 84 | [Parameter(Mandatory = $false)][switch]$ReDownloadIsHashIsDifferent 85 | ) 86 | 87 | function Invoke-BasicHTMLParser ($html) { 88 | $html = $html.Replace("
", "`r`n").Replace("
", "`r`n").Replace("
", "`r`n") # replace
with new line 89 | 90 | # Speaker Spacing 91 | $html = $html.Replace("
", "`r`n`r`n") 92 | 93 | # Link parsing 94 | $linkregex = '(?.*?)".*?>(?.*?)<\/a>)' 95 | $links = [regex]::Matches($html, $linkregex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 96 | foreach ($l in $links) { 97 | if (-not $l.Groups['link'].Value.StartsWith("http")) { $link = "$SchedBaseURL/$($l.Groups['link'].Value)" }else { $link = $l.Groups['link'].Value } 98 | $html = $html.Replace($l.Groups['texttoreplace'].Value, " [$($l.Groups['content'].Value)]($link)") 99 | } 100 | 101 | # List Parsing 102 | $listRegex = '(?]?>(?.*?)<\/ul>)' 103 | $lists = [regex]::Matches($html, $listRegex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 104 | foreach ($l in $lists) { 105 | $content = $l.Groups['content'].Value.Replace("
  • ", "`r`n* ").Replace("
  • ", "") 106 | $html = $html.Replace($l.Groups['texttoreplace'].Value, $content) 107 | } 108 | 109 | # General Cleanup 110 | $html = $html.replace("→", "") 111 | $html = $html -replace ']+>', "`r`n" 112 | $html = $html -replace '<[^>]+>', '' # Strip all HTML tags 113 | 114 | ## Future revisions 115 | # do something about / / / etc... 116 | # maybe a converter to markdown 117 | 118 | return $html 119 | } 120 | ## Hide Invoke-WebRequest progress bar. There's a bug that doesn't clear the bar after a request is finished. 121 | $ProgressPreference = "SilentlyContinue" 122 | ## Determine OS... sorta 123 | if ($PSEdition -eq "Desktop" -or $isWindows) { $win = $true } 124 | else { 125 | $win = $false 126 | if ($DownloadLocation -eq "C:\Conferences\MMS") { $DownloadLocation = "$HOME\Downloads\MMSContent" } 127 | } 128 | 129 | ## Make sure there aren't any trailing backslashes: 130 | $DownloadLocation = $DownloadLocation.Trim('\') 131 | 132 | ## Setup 133 | $PublicContentYears = @('2015', '2016', '2017', '2019', 'jazz', 'miami', '2022atmoa', '2023atmoa','2023miami', '2024atmoa', '2024fll') 134 | $PrivateContentYears = @('2018', 'de2018', '2025atmoa') 135 | $ConferenceYears = New-Object -TypeName System.Collections.Generic.List[string] 136 | [int]$PublicYearsCount = $PublicContentYears.Count 137 | [int]$PrivateYearsCount = $PrivateContentYears.Count 138 | 139 | if ($All) { 140 | for ($i = 0; $i -lt $PublicYearsCount; $i++) { 141 | $ConferenceYears.Add($PublicContentYears[$i]) 142 | } 143 | Remove-Variable -Name i -ErrorAction SilentlyContinue 144 | for ($i = 0; $i -lt $PrivateYearsCount; $i++) { 145 | $ConferenceYears.Add($PrivateContentYears[$i]) 146 | } 147 | Remove-Variable -Name i -ErrorAction SilentlyContinue 148 | } 149 | elseif ($PsCmdlet.ParameterSetName -eq 'SingleEvent') { 150 | $ConferenceYears.Add($ConferenceId) 151 | } 152 | else { 153 | $ConfListCount = $ConferenceList.Count 154 | for ($i = 0; $i -lt $ConfListCount; $i++) { 155 | if ($ConferenceList[$i] -in ($PublicContentYears + $PrivateContentYears)) { 156 | $ConferenceYears.Add($ConferenceList[$i]) 157 | } 158 | else { 159 | Write-Output "The Conference Id '$($ConferenceList[$i])' is not valid. Item will be skipped." 160 | } 161 | } 162 | Remove-Variable -Name i -ErrorAction SilentlyContinue 163 | } 164 | 165 | Write-Output "Base Download URL is $DownloadLocation" 166 | Write-Output "Searching for content from these sessions: $([String]::Join(',',$ConferenceYears))" 167 | 168 | ## 169 | $ConferenceYears | ForEach-Object -Process { 170 | [string]$Year = $_ 171 | 172 | if ($Year -in $PrivateContentYears) { 173 | $creds = $host.UI.PromptForCredential('Sched Credentials', "Enter Credentials for the MMS Event: $Year", '', '') 174 | } 175 | 176 | $SchedBaseURL = "https://mms" + $Year + ".sched.com" 177 | $SchedLoginURL = $SchedBaseURL + "/login" 178 | Add-Type -AssemblyName System.Web 179 | $web = Invoke-WebRequest $SchedLoginURL -SessionVariable mms -UseBasicParsing 180 | ## Connect to Sched 181 | 182 | if ($creds) { 183 | #$form = $web.Forms[1] 184 | #$form.fields['username'] = $creds.UserName; 185 | #$form.fields['password'] = $creds.GetNetworkCredential().Password; 186 | 187 | $username = $creds.UserName 188 | $password = $creds.GetNetworkCredential().Password 189 | 190 | # Updated POST body 191 | $body = "landing_conf=" + [System.Uri]::EscapeDataString($SchedBaseURL) + "&username=" + [System.Uri]::EscapeDataString($username) + "&password=" + [System.Uri]::EscapeDataString($password) + "&login=" 192 | 193 | # SEND IT 194 | $web = Invoke-WebRequest $SchedLoginURL -SessionVariable mms -Method POST -Body $body -UseBasicParsing 195 | 196 | } 197 | else { 198 | $web = Invoke-WebRequest $SchedLoginURL -SessionVariable mms -UseBasicParsing 199 | } 200 | 201 | $SessionDownloadPath = $DownloadLocation + '\mms' + $Year 202 | Write-Output "Logging in to $SchedBaseURL" 203 | 204 | ## Check if we connected (if required): 205 | if ((-Not ($web.InputFields.FindByName("login")) -and ($Year -in $PrivateContentYears)) -or ($Year -in $PublicContentYears)) { 206 | ## 207 | Write-Output "Downloaded content can be found in $SessionDownloadPath" 208 | 209 | $sched = Invoke-WebRequest -Uri $($SchedBaseURL + "/list/descriptions") -WebSession $mms -UseBasicParsing 210 | $links = $sched.Links 211 | # For indexing available downloads later 212 | $eventsList = New-Object -TypeName System.Collections.Generic.List[int] 213 | $links | ForEach-Object -Process { 214 | if ($_.href -like "event/*") { 215 | [void]$eventsList.Add($links.IndexOf($_)) 216 | } 217 | } 218 | $eventCount = $eventsList.Count 219 | 220 | for ($i = 0; $i -lt $eventCount; $i++) { 221 | [int]$linkIndex = $eventsList[$i] 222 | [int]$nextLinkIndex = $eventsList[$i + 1] 223 | $eventobj = $links[($eventsList[$i])] 224 | 225 | # Get/Fix the Session Title: 226 | $titleRegex = '.*?)".*?>(?.*?)(<span|<\/a>)' 227 | $titleMatches = [regex]::Matches($eventobj.outerHTML.Replace("`r", "").Replace("`n", ""), $titleRegex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 228 | [string]$eventTitle = $titleMatches.Groups[0].Groups['title'].Value.Trim() 229 | [string]$eventUrl = $titleMatches.Groups[0].Groups['url'].Value.Trim() 230 | 231 | # Generate session info string 232 | [string]$sessionInfoText = "" 233 | $sessionInfoText += "Session Title: `r`n$eventTitle`r`n`r`n" 234 | $downloadTitle = $eventTitle -replace "[^A-Za-z0-9-_. ]", "" 235 | $downloadTitle = $downloadTitle.Trim() 236 | $downloadTitle = $downloadTitle -replace "\W+", "_" 237 | 238 | ## Set the download destination: 239 | $downloadPath = $SessionDownloadPath + "\" + $downloadTitle 240 | 241 | ## Get session info if required: 242 | if (-not $ExcludeSessionDetails) { 243 | try{ 244 | #Wait-Debugger 245 | $sessionLinkInfo = (Invoke-WebRequest -Uri $($SchedBaseURL + "/" + $eventUrl) -WebSession $mms -UseBasicParsing).Content.Replace("`r", "").Replace("`n", "") 246 | } 247 | catch { 248 | if (($_.Exception.GetType().FullName -eq "System.Net.WebException" -or $_.Exception.GetType().FullName -eq "Microsoft.PowerShell.Commands.HttpResponseException") ` 249 | -and $_.Exception.Response.StatusCode -eq 429) { 250 | Write-Warning "Received 429 Too Many Requests error. Waiting 20 seconds and retrying..." 251 | Start-Sleep -Seconds 20 252 | $sessionLinkInfo = (Invoke-WebRequest -Uri $($SchedBaseURL + "/" + $eventUrl) -WebSession $mms -UseBasicParsing).Content.Replace("`r", "").Replace("`n", "") 253 | } 254 | } 255 | 256 | $descriptionPattern = '<div class="tip-description">(?<description>.*?)(<div class="tip-roles">|<div class="sched-event-details-timeandplace">)' 257 | $description = [regex]::Matches($sessionLinkInfo, $descriptionPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 258 | if ($description.Count -gt 0) { $sessionInfoText += "$(Invoke-BasicHTMLParser -html $description.Groups[0].Groups['description'].Value)`r`n`r`n" } 259 | 260 | $rolesPattern = '<div class="tip-roles">(?<roles>.*?)<div class="sched-file">|<div class="sched-event-details-timeandplace">' 261 | $roles = [regex]::Matches($sessionLinkInfo, $rolesPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 262 | if ($roles.Count -gt 0) { $sessionInfoText += "$(Invoke-BasicHTMLParser -html $roles.Groups[0].Groups['roles'].Value)`r`n`r`n" } 263 | 264 | if ((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 265 | Out-File -FilePath "$downloadPath\Session Info.txt" -InputObject $sessionInfoText -Force -Encoding default 266 | } 267 | 268 | $downloads = $links[($linkIndex + 1)..($nextLinkIndex - 1)] | Where-Object { $_.href -like "*hosted_files*" } #prefilter 269 | foreach ($download in $downloads) { 270 | $filename = Split-Path $download.href -Leaf 271 | # Replace HTTP Encoding Characters (e.g. %20) with the proper equivalent. 272 | $filename = [System.Web.HttpUtility]::UrlDecode($filename) 273 | # Replace non-standard characters 274 | $filename = $filename -replace "[^A-Za-z0-9\.\-_ ]", "" 275 | 276 | $outputFilePath = $downloadPath + '\' + $filename 277 | 278 | # Reduce Total Path to 255 characters. 279 | $outputFilePathLen = $outputFilePath.Length 280 | if ($outputFilePathLen -ge 255) { 281 | $fileExt = [System.IO.Path]::GetExtension($outputFilePath) 282 | $newFileName = $outputFilePath.Substring(0, $($outputFilePathLen - $fileExt.Length)) 283 | $newFileName = $newFileName.Substring(0, $(255 - $fileExt.Length)).trim() 284 | $newFileName = "$newFileName$fileExt" 285 | $outputFilePath = $newFileName 286 | } 287 | 288 | # Download the file 289 | if ((Test-Path -Path $($downloadPath)) -eq $false) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } 290 | if ((Test-Path -Path $outputFilePath) -eq $false) { 291 | Write-host -ForegroundColor Green "...attempting to download '$filename' because it doesn't exist" 292 | try { 293 | try{ 294 | Invoke-WebRequest -Uri $download.href -OutFile $outputfilepath -WebSession $mms -UseBasicParsing 295 | } 296 | catch { 297 | if (($_.Exception.GetType().FullName -eq "System.Net.WebException" -or $_.Exception.GetType().FullName -eq "Microsoft.PowerShell.Commands.HttpResponseException") ` 298 | -and $_.Exception.Response.StatusCode -eq 429) { 299 | Write-Warning "Received 429 Too Many Requests error. Waiting 20 seconds and retrying..." 300 | Start-Sleep -Seconds 20 301 | Invoke-WebRequest -Uri $download.href -OutFile $outputfilepath -WebSession $mms -UseBasicParsing 302 | } 303 | } 304 | if ($win) { Unblock-File $outputFilePath } 305 | } 306 | catch { 307 | Write-Output ".................$($PSItem.Exception) for '$($download.href)'...moving to next file..." 308 | } 309 | } 310 | else { 311 | if ($ReDownloadIsHashIsDifferent) { 312 | Write-Output "...attempting to download '$filename'" 313 | $oldHash = (Get-FileHash $outputFilePath).Hash 314 | try { 315 | try{ 316 | Invoke-WebRequest -Uri $download.href -OutFile "$($outputfilepath).new" -WebSession $mms -UseBasicParsing 317 | } 318 | catch { 319 | if (($_.Exception.GetType().FullName -eq "System.Net.WebException" -or $_.Exception.GetType().FullName -eq "Microsoft.PowerShell.Commands.HttpResponseException") ` 320 | -and $_.Exception.Response.StatusCode -eq 429) { 321 | Write-Warning "Received 429 Too Many Requests error. Waiting 20 seconds and retrying..." 322 | Start-Sleep -Seconds 20 323 | Invoke-WebRequest -Uri $download.href -OutFile "$($outputfilepath).new" -WebSession $mms -UseBasicParsing 324 | } 325 | } 326 | if ($win) { Unblock-File "$($outputfilepath).new" } 327 | $NewHash = (Get-FileHash "$($outputfilepath).new").Hash 328 | if ($NewHash -ne $oldHash) { 329 | Write-Host -ForegroundColor Green " => HASH is different. Keeping new file" 330 | Move-Item "$($outputfilepath).new" $outputfilepath -Force 331 | } 332 | else { 333 | Write-Output " => Hash is the same. " 334 | Remove-item "$($outputfilepath).new" -Force 335 | } 336 | } 337 | catch { 338 | Write-Output ".................$($PSItem.Exception) for '$($download.href)'...moving to next file..." 339 | } 340 | } 341 | } 342 | } # end procesing downloads 343 | } # end processing session 344 | } # end connectivity/login check 345 | else { 346 | Write-Output "Login to $SchedBaseUrl failed." 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get-MMSSessionContent 2 | 3 | These scripts are used to download the session files that were made available during [Midwest Management Summit 2015-2025](http://mmsmoa.com). If you are involved in configuration/infrastructure/identity/mobile device management primarily in the Microsoft space, consider attending this event! 4 | 5 | ## Usage 6 | 7 | For content just from 2024 in a custom directory (default is C:\Conferences\MMS\$conferenceyear), use the following: 8 | 9 | ``` .\Get-MMSSessionContent.ps1 -DownloadLocation "C:\Temp\MMS" -ConferenceId 2025atmoa``` 10 | 11 | For multiple years: 12 | 13 | ``` .\Get-MMSSessionContent.ps1 -ConferenceList @('2015','2025atmoa')``` 14 | 15 | To exclude session details: 16 | 17 | ``` .\Get-MMSSessionContent.ps1 -All -ExcludeSessionDetails``` 18 | 19 | ## Acknowledgements 20 | 21 | Thank you to: 22 | - [Duncan Russell](http://www.sysadmintechnotes.com/) for providing the initial script for MMS 2014 and helping me test the changes I made for it to work with the more recent conferences. 23 | - [Evan Yeung](https://github.com/forevanyeung) for cleaning up processing and file naming. 24 | - [Chris Kibble](https://www.christopherkibble.com) for continued testing and improvements made to the script. 25 | - [Benjamin Reynolds](https://sqlbenjamin.wordpress.com) for loads of great changes and additional testing. 26 | - [Nathan Ziehnert](https://z-nerd.com/) for adding PowerShell 7 support 27 | - [Piotr Gardy](https://github.com/pgardy) for adding file hash comparing 28 | - As well as edits by [Jon Warnken](https://github.com/mrbodean), [Oliver Baddeley](https://github.com/BaddMann), and [Jorge Suarez](https://github.com/jorgeasaurus) 29 | 30 | This script is provided as-is with no guarantees. As of May 1, 2025, version 1.7 was tested with no errors using the following configurations: 31 | 32 | - Windows 11 24H2 using Windows PowerShell 5.1 33 | - Windows 11 24H2 using PowerShell 7.4.5 34 | - Ubuntu using PowerShell 7.4.5 35 | --------------------------------------------------------------------------------