├── README.md ├── StartMenuLayout ├── Get-Apps-and-IDs.ps1 └── Create-Start-Menu-Layout-XML.ps1 ├── Rename-PreExistingWIM.ps1 ├── Configure-Existing-User-Registry-Settings.ps1 ├── Outlook-VMMap-Snapshot.ps1 └── Get-DellDownloads.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell -------------------------------------------------------------------------------- /StartMenuLayout/Get-Apps-and-IDs.ps1: -------------------------------------------------------------------------------- 1 | $LayoutPath = "C:\StartMenu" 2 | $StartAppsFile = "StartApps.csv" 3 | $PublisherIDsFile = "PublisherIDs.txt" 4 | 5 | Get-StartApps | Export-Csv -Path $LayoutPath\$StartAppsFile 6 | 7 | Get-AppxPackage | Select-Object -ExpandProperty PublisherID | Sort-Object | Get-Unique | Out-File -FilePath $LayoutPath\$PublisherIDsFile 8 | -------------------------------------------------------------------------------- /Rename-PreExistingWIM.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Author: Kevin Johnston 3 | Date: 10/5/2017 4 | 5 | Checks for a preexisting WIM file in the captures folder with the 6 | same name as the one to be captured and renames it. This prevents 7 | the current capture from being added to the existing WIM. 8 | #> 9 | 10 | $TSEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment 11 | $WIMFileName = $TSEnv.Value("BackupFile") 12 | $WIMPath = $TSEnv.Value("ComputerBackupLocation") + "\" + $WIMFileName 13 | 14 | if (Test-Path -Path $WIMPath) 15 | { 16 | $WIMNewName = $WIMFileName + ".bak" + (Get-Date -Format yyyyMMdd-HHmmss) 17 | Rename-Item -Path $WIMPath -NewName $WIMNewName 18 | } -------------------------------------------------------------------------------- /Configure-Existing-User-Registry-Settings.ps1: -------------------------------------------------------------------------------- 1 | # Set path and name variables 2 | $ProfileRegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" 3 | $RegLoadName = 1 4 | $HiveFile = "NTUSER.DAT" 5 | $RegLoadPath = "registry::HKEY_USERS" 6 | 7 | $RunRegPath = "SOFTWARE\Microsoft\Windows\Currentversion\Run" 8 | $PeopleRegPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" 9 | $NotificationsRegPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings" 10 | $PushNotifRegPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\PushNotifications\Backup" 11 | 12 | $ODRegProp = "OneDriveSetup" 13 | 14 | $PeopleRegKey = "People" 15 | $PeopleBandRegProp = "PeopleBand" 16 | 17 | $LockScreenRegProp = "NOC_GLOBAL_SETTING_ALLOW_TOASTS_ABOVE_LOCK" 18 | $LockVOIPRegProp = "NOC_GLOBAL_SETTING_ALLOW_CRITICAL_TOASTS_ABOVE_LOCK" 19 | $DupScreenRegProp = "NOC_GLOBAL_SETTING_SUPRESS_TOASTS_WHILE_DUPLICATING" 20 | 21 | $SugRegKey = "Windows.SystemToast.Suggested" 22 | 23 | $SugAppTypeRegProp = "appType" 24 | $SugAppTypeRegValue = "app:system" 25 | 26 | $SugWnsldRegProp = "wnsld" 27 | $SugWnsldRegValue = "System" 28 | 29 | $SugSettingRegProp = "Setting" 30 | $SugSettingRegValue = "c:toast,c:ringing,c:storage:toast,s:tickle,s:toast,s:audio,s:badge,s:lock:badge,s:banner,s:listenerEnabled,s:lock:tile,s:tile,s:lock:toast,s:voip" 31 | 32 | # Get profile path info for all user profiles with SIDs beginning with S-1-5-21 (normal domain and local users) 33 | $ProfileKeys = Get-ChildItem -Path $ProfileRegPath 34 | $Profiles = $ProfileKeys | ForEach-Object {Get-ItemProperty -Path $_.PSPath | Where-Object {$_ -match "S-1-5-21-*"}} 35 | 36 | # For each profile, mount the NTUSER.DAT hive, delete the OneDriveSetup Run property if found, and unmount the hive 37 | foreach ($Profile in $Profiles) { 38 | 39 | $ImagePath = $Profile.ProfileImagePath 40 | 41 | reg load HKU\$($RegLoadName.ToString()) $ImagePath\$HiveFile | Out-Null 42 | 43 | # Checks for the existence of the OneDriveSetup property, whether it has a value or not 44 | if ((Get-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$RunRegPath).PSObject.Properties.Name -contains $ODRegProp) { 45 | 46 | Remove-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$RunRegPath -Name $ODRegProp 47 | } 48 | 49 | # Add People key and PeopleBand property to disable the taskbar people button 50 | New-Item -Path $RegLoadPath\$($RegLoadName.ToString())\$PeopleRegPath -Name $PeopleRegKey -Force 51 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$PeopleRegPath\$PeopleRegKey -Name $PeopleBandRegProp -PropertyType DWORD -Value 0 -Force 52 | 53 | # Configure notification settings to hide on lock screen and when duplicating the screen 54 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$NotificationsRegPath -Name $LockScreenRegProp -PropertyType DWORD -Value 0 -Force 55 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$NotificationsRegPath -Name $LockVOIPRegProp -PropertyType DWORD -Value 0 -Force 56 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$NotificationsRegPath -Name $DupScreenRegProp -PropertyType DWORD -Value 1 -Force 57 | 58 | # Turn off notifications from the "Suggested" sender 59 | New-Item -Path $RegLoadPath\$($RegLoadName.ToString())\$NotificationsRegPath -Name $SugRegKey -Force 60 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$NotificationsRegPath\$SugRegKey -Name Enabled -PropertyType DWORD -Value 0 -Force 61 | 62 | # Configure the "Suggested" sender to show in the "Get notifications from these senders" list 63 | New-Item -Path $RegLoadPath\$($RegLoadName.ToString())\$PushNotifRegPath -Name $SugRegKey -Force 64 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$PushNotifRegPath\$SugRegKey -Name $SugAppTypeRegProp -PropertyType String -Value $SugAppTypeRegValue -Force 65 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$PushNotifRegPath\$SugRegKey -Name $SugWnsldRegProp -PropertyType String -Value $SugWnsldRegValue -Force 66 | New-ItemProperty -Path $RegLoadPath\$($RegLoadName.ToString())\$PushNotifRegPath\$SugRegKey -Name $SugSettingRegProp -PropertyType String -Value $SugSettingRegValue -Force 67 | 68 | # Run garbage collection to free up any handles that may prevent the hive from successfully unloading 69 | [gc]::Collect() 70 | reg unload HKU\$($RegLoadName.ToString()) | Out-Null 71 | 72 | # Increment the number used for the load key name so it is unique for each iteration 73 | $RegLoadName++ 74 | } -------------------------------------------------------------------------------- /Outlook-VMMap-Snapshot.ps1: -------------------------------------------------------------------------------- 1 | # Author: Kevin Johnston 2 | # Date: April 7, 2016 3 | # 4 | # Read my blogpost linked below for more details: 5 | # https://ccmcache.wordpress.com/2016/04/07/use-powershell-vmmap-and-debugdiag-to-reproduce-and-identify-a-virtual-memory-fragmentation-issue-causing-performance-problems-in-outlook/ 6 | # 7 | # This script performs the following actions: 8 | # 9 | # 1. Opens/Displays/Renders and closes an Outlook message for a defined number of cycles 10 | # 2. Runs VMMap at a defined cycle interval to generate .mmp (virtual memory snapshot) files 11 | # 3. Parses the .mmp XML content to find the count of 4KB private data allocations as well as unusable and non-free virtual memory 12 | # 4. Outputs cycle progress and VMMap information to the console 13 | # 14 | # Tested with Outlook 2010*, 2013, and 2016 15 | # *Please see the note on line 37 regarding method change for Outlook 2010 16 | 17 | 18 | $cycles = 500 # The maximum number of open/close message cycles 19 | $vmmapinterval = 50 # The cycle interval at which VMMap will run and generate a .mmp file 20 | $vmmapfolder = "C:\Temp\vmmap" # The location of VMMap.exe and the save location for .mmp files 21 | $mailboxname = "email@yourcompany.com" # The desired Outlook mailbox Name (Likely your email address) 22 | $mailfoldername = "Inbox" # The desired mailbox folder name 23 | 24 | # Create the Outlook COM object and get the messaging API namespace 25 | $outlook = New-Object -ComObject Outlook.Application 26 | $namespace = $outlook.GetNamespace("MAPI") 27 | 28 | # Create the mailbox and mailfolder objects 29 | $mailbox = $namespace.Folders | Where-Object {$_.Name -eq $mailboxname} 30 | $mailfolder = $mailbox.Folders.Item($mailfoldername) 31 | 32 | # Display the Outlook main window 33 | $explorer = $mailfolder.GetExplorer() 34 | $explorer.Display() 35 | 36 | # Create the message object 37 | $message = $mailfolder.Items.GetLast() # Change to .GetFirst() method if using Outlook 2010, otherwise .Close() method will not work 38 | 39 | # Add the assembly needed to create the OlInspectorClose object for the .Close() method 40 | Add-Type -Assembly "Microsoft.Office.Interop.Outlook" 41 | $discard = [Microsoft.Office.Interop.Outlook.OlInspectorClose]::olDiscard 42 | 43 | #------------------------------------------------------------------------------------------------------------------------------------- 44 | # Execute the above code first, wait for the Outlook window to display, and reposition it if necessary before executing the below code 45 | #------------------------------------------------------------------------------------------------------------------------------------- 46 | 47 | for ($i = 1; $i -lt ($cycles + 1) ; $i++) 48 | { 49 | # Open the message then close and discard changes 50 | $message.Display() 51 | $message.Close($discard) 52 | 53 | Write-Progress -Activity "Working..." -Status "$i of $cycles cycles complete" -PercentComplete (($i / $cycles) * 100) 54 | 55 | if ($i % $vmmapinterval -eq 0) 56 | { 57 | # Run VMMap map with the necessary command line options and generate .mmp file 58 | Start-Process -Wait -FilePath $vmmapfolder\vmmap.exe -ArgumentList "-accepteula -p outlook.exe outputfile $vmmapfolder\outlook$i.mmp" -WindowStyle Hidden 59 | 60 | # Get .mmp file content as XML 61 | [xml]$vmmap = Get-Content $vmmapfolder\outlook$i.mmp 62 | $regions = $vmmap.root.Snapshots.Snapshot.MemoryRegions.Region 63 | 64 | # Get Count of 4KB private data allocations 65 | $privdata4k = ($regions | Where-Object {($_.Type -eq "Private Data") -and ($_.Size -eq "4096")}).Count 66 | 67 | # Get Unusable and non-free virtual memory totals 68 | $unusablevm = ((($regions | Where-Object {$_.Type -eq "Unusable"}).Size | Measure-Object -Sum).Sum / 1MB) 69 | $nonfreevm = ((($regions | Where-Object {$_.Type -ne "Free"}).Size | Measure-Object -Sum).Sum / 1GB) 70 | 71 | # Round results to two decimal places 72 | $unusablevmrounded = [math]::Round($unusablevm,2) 73 | $nonfreevmrounded = [math]::Round($nonfreevm,2) 74 | 75 | Write-Output "-----------------------------------------------------------------------" 76 | Write-Output " $privdata4k 4KB Private Data Allocations and" 77 | Write-Output " $unusablevmrounded MBs of Unusable Memory After $i Open/Close Cycles" 78 | Write-Output " $nonfreevmrounded GB of 2GB Virtual Memory Limit Reached" 79 | Write-Output "-----------------------------------------------------------------------" 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /StartMenuLayout/Create-Start-Menu-Layout-XML.ps1: -------------------------------------------------------------------------------- 1 | # Set file and folder names 2 | $LayoutPath = "C:\StartMenu" 3 | $LayoutXMLFile = "Layout.xml" 4 | $StartAppsFile = "StartApps.csv" 5 | $PublisherIDsFile = "PublisherIDs.txt" 6 | 7 | # Set the output location of the layout xml file 8 | $XmlPath = "$LayoutPath\$LayoutXMLFile" 9 | 10 | # Get the text files containing groups of apps to be pinned 11 | # The files must be named GROUPNAME.1 and GROUPNAME.2 12 | # where GROUPNAME = the desired display name of the group in the start menu 13 | $GroupFiles = Get-ChildItem -Path $LayoutPath\* -Include *.1, *.2 14 | 15 | # Get the Name (file name + extension) and BaseName (file name only) properties of the group files 16 | $Group1 = $GroupFiles | Where-Object {$_.Name -match ".1"} | Select-Object -Property Name,BaseName 17 | $Group2 = $GroupFiles | Where-Object {$_.Name -match ".2"} | Select-Object -Property Name,BaseName 18 | 19 | function WriteShellXML { 20 | 21 | # Write the required XML elements for the Start layout file 22 | $writer.WriteStartElement("LayoutModificationTemplate", "http://schemas.microsoft.com/Start/2014/LayoutModification") 23 | $writer.WriteAttributeString("Version", "1") 24 | 25 | $writer.WriteStartElement("DefaultLayoutOverride") 26 | $writer.WriteAttributeString("LayoutCustomizationRestrictionType", "OnlySpecifiedGroups") 27 | 28 | $writer.WriteStartElement("StartLayoutCollection") 29 | 30 | $writer.WriteStartElement("defaultlayout", "StartLayout", "http://schemas.microsoft.com/Start/2014/FullDefaultLayout") 31 | $writer.WriteAttributeString("GroupCellWidth", "6") 32 | } 33 | 34 | function WriteTileXML ($Group) { 35 | 36 | switch ($Group) { 37 | 38 | 1 {$GroupNumber = $Group1} 39 | 2 {$GroupNumber = $Group2} 40 | } 41 | 42 | # Get the list of apps for the designated group 43 | $GroupApps = Get-Content -Path $LayoutPath\$($GroupNumber.Name) 44 | 45 | # Set the group name to be displayed in the Start Menu 46 | $StartGroupName = $GroupNumber.BaseName 47 | 48 | # Write the Start Group XML Element 49 | $writer.WriteStartElement("start", "Group", "http://schemas.microsoft.com/Start/2014/StartLayout") 50 | $writer.WriteAttributeString("Name", $StartGroupName) 51 | 52 | # Set loop counter 53 | $Counter = 0 54 | 55 | # Loop through the group apps list and write corresponding XML elements 56 | foreach ($Item in $GroupApps) { 57 | 58 | # Get the start app info for the pinned list item 59 | $StartApp = $StartAppsCSV | Where-Object {$_.Name -eq $Item} 60 | 61 | # Check for existence of start app 62 | if ($StartApp) { 63 | 64 | # Determine modern or desktop app, set corresponding tile and appID types 65 | if (($PubIDs | ForEach-Object {$StartApp.AppID -match $_} ) -contains $true) { 66 | 67 | $TileType = "Tile" 68 | $AppIDType = "AppUserModelID" 69 | } 70 | else { 71 | 72 | $TileType = "DesktopApplicationTile" 73 | $AppIDType = "DesktopApplicationID" 74 | } 75 | 76 | # Determine column and row value of the tile 77 | switch ($Counter) { 78 | 79 | 0 {$column = 0; $row = 0} 80 | 1 {$column = 2; $row = 0} 81 | 2 {$column = 4; $row = 0} 82 | 3 {$column = 0; $row = 2} 83 | 4 {$column = 2; $row = 2} 84 | 5 {$column = 4; $row = 2} 85 | 6 {$column = 0; $row = 4} 86 | 7 {$column = 2; $row = 4} 87 | 8 {$column = 4; $row = 4} 88 | } 89 | 90 | # Write XML elements and attributes for the tile 91 | $writer.WriteStartElement("start", $TileType, "http://schemas.microsoft.com/Start/2014/StartLayout") 92 | $writer.WriteAttributeString("Size", "2x2") 93 | $writer.WriteAttributeString("Column", $column) 94 | $writer.WriteAttributeString("Row", $row) 95 | $writer.WriteAttributeString($AppIDType, $StartApp.AppID) 96 | $writer.WriteEndElement() 97 | 98 | # Increment loop counter 99 | $Counter++ 100 | } 101 | } 102 | } 103 | # If Group 1 exists, proceed. Else, do nothing 104 | if ($Group1) { 105 | 106 | # Get Start Apps list 107 | $StartAppsCSV = Import-Csv -Path $LayoutPath\$StartAppsFile 108 | 109 | # Get Modern App publisher IDs 110 | $PubIDs = Get-Content -Path $LayoutPath\$PublisherIDsFile 111 | 112 | # Create a new XML writer settings object and configure settings 113 | $settings = New-Object system.Xml.XmlWriterSettings 114 | $settings.Indent = $true 115 | $settings.OmitXmlDeclaration = $true 116 | 117 | # Create a new XML writer 118 | $writer = [system.xml.XmlWriter]::Create($XmlPath, $settings) 119 | 120 | # Call function to write XML shell 121 | WriteShellXML 122 | 123 | # Call function to write tile XML elements 124 | WriteTileXML -Group 1 125 | 126 | # Only executed if Group 2 exists 127 | if ($Group2) { 128 | 129 | # Write additional end element for Group 1 130 | $writer.WriteEndElement() 131 | 132 | WriteTileXML -Group 2 133 | } 134 | 135 | # Flush the XML writer and close the file 136 | $writer.Flush() 137 | $writer.Close() 138 | } 139 | 140 | -------------------------------------------------------------------------------- /Get-DellDownloads.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Use this cmdlet to discover and download Dell BIOS/Driver updates 4 | .DESCRIPTION 5 | This cmdlet requests website data from Dell's drivers & downloads simplified interface site (http://downloads.dell.com/published/Pages/) 6 | and enables you to discover and download related files for a specific model or list of models 7 | .EXAMPLE 8 | Get a list of all available BIOS downloads for a specific model and present the results in a gridview: 9 | 10 | Get-DellDownloads -ModelName "Latitude E7470" -Category BIOS -Type BIOS 11 | .EXAMPLE 12 | Get a list of all available video driver downloads for a specific model and present the results in a gridview, including the description column. Download the selected files from that list to the location specified: 13 | 14 | Get-DellDownloads -ModelName "Latitude E7240 Ultrabook" -Category Video -Type Driver -IncludeDescription -DownloadLocation C:\Temp 15 | .EXAMPLE 16 | For a provided text file containing a list of multiple models, find possible categories/types and present the results in a griview. Based on the selection from that list, get a list of the most recent downloads of that category/type for each model, and present a gridview list of the results. Download the selected files from that list to the location specified: 17 | 18 | Get-DellDownloads -ModelList C:\Temp\ModelList.txt -DownloadLocation C:\Temp 19 | .EXAMPLE 20 | For a provided text file containing a list of multiple models, get a list of the most recent BIOS downloads for each model and present the results in a gridview: 21 | 22 | Get-DellDownloads -ModelList C:\Temp\ModelList.txt -Category BIOS -Type BIOS 23 | .NOTES 24 | Author: Kevin Johnston 25 | Date: December 10, 2018 26 | #> 27 | function Get-DellDownloads 28 | { 29 | [CmdletBinding()] 30 | Param 31 | ( 32 | [parameter(Mandatory=$true,ParameterSetName="Name")][String]$ModelName, 33 | [parameter(Mandatory=$true,ParameterSetName="List")][String]$ModelList, 34 | [parameter(Mandatory=$false)] 35 | [ValidateSet("Application","Audio","Backup and Recovery","BIOS","Chipset","CMDSK","Communications",` 36 | "Dell Data Protection","Docks/Stands","Drivers for OS Deployment","Firmware","IDM","Input",` 37 | "Miscellaneous Utilities","Network","Removeable Storage","SAS Drive","SAS RAID","Security Encryption",` 38 | "Serial ATA","Storage","Storage Controller","Systems Management","System Utilities","Video")] 39 | [String]$Category, 40 | [parameter(Mandatory=$false)] 41 | [ValidateSet("Application","BIOS","Diagnostics Utility","Driver","Firmware","HTML","ISV Driver","Utility")] 42 | [String]$Type, 43 | [switch]$IncludeDescription, 44 | [parameter(Mandatory=$false)][String]$DownloadLocation 45 | ) 46 | 47 | # set models variable to single model or list depending on parameter used 48 | if ($ModelName) {$models = $ModelName} 49 | if ($ModelList) {$models = Get-Content -Path $ModelList} 50 | 51 | if ($Category -and $Type) 52 | { 53 | # create array of known SectionID, Category, and Type info 54 | $knownSections = @" 55 | "Drivers-Category.AP-Type.APP","Application","Application" 56 | "Drivers-Category.AS-Type.FRMW","SAS Drive","Firmware" 57 | "Drivers-Category.AU-Type.DRVR","Audio","Driver" 58 | "Drivers-Category.BI-Type.BIOS","BIOS","BIOS" 59 | "Drivers-Category.BR-Type.APP","Backup and Recovery","Application" 60 | "Drivers-Category.CM-Type.APP","Communications","Application" 61 | "Drivers-Category.CM-Type.DRVR","Communications","Driver" 62 | "Drivers-Category.CM-Type.UTIL","Communications","Utility" 63 | "Drivers-Category.CS-Type.APP","Chipset","Application" 64 | "Drivers-Category.CS-Type.DRVR","Chipset","Driver" 65 | "Drivers-Category.CS-Type.FRMW","Chipset","Firmware" 66 | "Drivers-Category.DD-Type.APP","Drivers for OS Deployment","Application" 67 | "Drivers-Category.DD-Type.DRVR","Drivers for OS Deployment","Driver" 68 | "Drivers-Category.DK-Type.DRVR","Docks/Stands","Driver" 69 | "Drivers-Category.DK-Type.FRMW","Docks/Stands","Firmware" 70 | "Drivers-Category.DK-Type.UTIL","Docks/Stands","Utility" 71 | "Drivers-Category.DP-Type.APP","Dell Data Protection","Application" 72 | "Drivers-Category.DP-Type.DRVR","Dell Data Protection","Driver" 73 | "Drivers-Category.FW-Type.FRMW","Firmware","Firmware" 74 | "Drivers-Category.IDM-Type.APP","IDM","Application" 75 | "Drivers-Category.IN-Type.APP","Input","Application" 76 | "Drivers-Category.IN-Type.DRVR","Input","Driver" 77 | "Drivers-Category.MU-Type.UTIL","Miscellaneous Utilities","Utility" 78 | "Drivers-Category.NI-Type.APP","Network","Application" 79 | "Drivers-Category.NI-Type.DIAG","Network","Diagnostics Utility" 80 | "Drivers-Category.NI-Type.DRVR","Network","Driver" 81 | "Drivers-Category.NI-Type.FRMW","Network","Firmware" 82 | "Drivers-Category.NI-Type.HTML","Network","HTML" 83 | "Drivers-Category.NI-Type.UTIL","Network","Utility" 84 | "Drivers-Category.RS-Type.FRMW","Removable Storage","Firmware" 85 | "Drivers-Category.SA-Type.DRVR","Serial ATA","Driver" 86 | "Drivers-Category.SA-Type.FRMW","Serial ATA","Firmware" 87 | "Drivers-Category.SA-Type.UTIL","Serial ATA","Utility" 88 | "Drivers-Category.SF-Type.APP","SAS RAID","Application" 89 | "Drivers-Category.SF-Type.FRMW","SAS RAID","Firmware" 90 | "Drivers-Category.SG-Type.APP","Storage Controller","Application" 91 | "Drivers-Category.SG-Type.DRVR","Storage Controller","Driver" 92 | "Drivers-Category.SK-Type.APP","CMDSK","Application" 93 | "Drivers-Category.SM-Type.APP","Systems Management","Application" 94 | "Drivers-Category.SM-Type.DRVR","Systems Management","Driver" 95 | "Drivers-Category.SM-Type.UTIL","Systems Management","Utility" 96 | "Drivers-Category.SP-Type.APP","Security Encryption","Application" 97 | "Drivers-Category.SP-Type.DRVR","Security Encryption","Driver" 98 | "Drivers-Category.ST-Type.DRVR","Storage","Driver" 99 | "Drivers-Category.SY-Type.DRVR","Security","Driver" 100 | "Drivers-Category.SY-Type.FRMW","Security","Firmware" 101 | "Drivers-Category.UT-Type.APP","System Utilities","Application" 102 | "Drivers-Category.UT-Type.DRVR","System Utilities","Driver" 103 | "Drivers-Category.UT-Type.UTIL","System Utilities","Utility" 104 | "Drivers-Category.VI-Type.APP","Video","Application" 105 | "Drivers-Category.VI-Type.DRVR","Video","Driver" 106 | "Drivers-Category.VI-Type.FRMW","Video","Firmware" 107 | "Drivers-Category.VI-Type.ISV","Video","ISV Driver" 108 | "Drivers-Category.VI-Type.UTIL","Video","Utility" 109 | "@ -split [System.Environment]::NewLine | ConvertFrom-Csv -Header SectionID,Category,Type 110 | 111 | # if the Category and Type parameters are used, set the corresponding SectionID variable 112 | $section = $knownSections | Where-Object {($_.Category -eq $Category) -and ($_.Type -eq $Type)} 113 | $sectionID = $section.SectionID 114 | } 115 | 116 | # set URI variables 117 | $baseURI = "http://downloads.dell.com/published/Pages/" 118 | $indexURI = $baseURI + "index.html" 119 | 120 | # request the download index webpage 121 | $dlIndex = Invoke-WebRequest -Uri $indexURI 122 | 123 | # get all links from the webpage 124 | $indexLinks = $dlIndex.Links 125 | 126 | # if the Category and Type paremeters were not used to define a SectionID, 127 | # request all possible values from the model(s) specified 128 | if (-not($sectionID)) 129 | { 130 | # initialize array to store final unique category/type/sectionID info 131 | $categoryResults = @() 132 | 133 | foreach ($model in $models) 134 | { 135 | # set the link variable for the specific model webpage 136 | $modelLink = $indexLinks | Where-Object {$_.innerHTML -eq $model} 137 | 138 | # set the URI variable for the specific model webpage 139 | $modelURI = $baseURI + $modelLink.href 140 | 141 | # request the specific model webpage 142 | $modelIndex = Invoke-WebRequest -Uri $modelURI 143 | 144 | Write-Output "Requesting available categories and types for model $model" 145 | 146 | # get webpage elements for the model sections 147 | $sectionIndex = $modelIndex.ParsedHTML.getElementsByTagName('DIV') 148 | 149 | # get all of the SectionIDs with '-Type.' in the name 150 | $typeIDs = $sectionIndex | Where-Object {$_.id -like '*-Type.*'} 151 | 152 | # get webpage elements for the model headings 153 | $headingIndex = $modelIndex.ParsedHTML.getElementsByTagName('H5') 154 | 155 | # initialize an empty array to store category/type/sectionID info 156 | $categories = @() 157 | 158 | # initialize a counter for the SectionID types array 159 | $idCounter = 0 160 | 161 | # loop through the model headings 162 | for ($headingCounter = 0; $headingCounter -lt ($headingIndex | Measure-Object).Count; $headingCounter++) 163 | { 164 | if ($headingIndex[$headingCounter].innerText -like 'Category:*') 165 | { 166 | # if the heading is a category, set the category text variable 167 | $charIndex = ($headingIndex[$headingCounter].innerText).IndexOf(':') 168 | $categoryText = ($headingIndex[$headingCounter].innerText).Substring($charIndex + 2) 169 | } 170 | 171 | if ($headingIndex[$headingCounter].innerText -like 'Type:*') 172 | { 173 | # if the heading is a type, set the type text variable 174 | $charIndex = ($headingIndex[$headingCounter].innerText).IndexOf(':') 175 | $typeText = ($headingIndex[$headingCounter].innerText).Substring($charIndex + 2) 176 | 177 | # set the corresponding sectionID text variable 178 | $sectionIDtext = $typeIDs[$idCounter].id 179 | 180 | # add category, type, and sectionID 181 | $categories += New-Object psobject -Property @{Category=$categoryText;Type=$typeText;SectionID=$sectionIDtext} 182 | 183 | # increment the sectionID types counter 184 | $idCounter++ 185 | } 186 | } 187 | 188 | # loop through the category/type/sectionID array 189 | foreach ($item in $categories) 190 | { 191 | # if the category results array does not already contain an entry with the element's section ID, add to the array 192 | if ($categoryResults.sectionID -notcontains $item.sectionID) 193 | { 194 | $categoryResults += $item 195 | } 196 | } 197 | } 198 | 199 | # display the results in a grid view and set the sectionID variable to the user's selection 200 | $sectionID = $categoryResults | Select-Object -Property Category,Type,SectionID | Sort-Object -Property Category | Out-GridView -PassThru | Select-Object -ExpandProperty SectionID 201 | } 202 | 203 | # initialize an empty array to store model results 204 | $modelResults = @() 205 | 206 | foreach ($model in $models) 207 | { 208 | # set the link variable for the specific model webpage 209 | $modelLink = $indexLinks | Where-Object {$_.innerHTML -eq $model} 210 | 211 | # set the URI variable for the specific model webpage 212 | $modelURI = $baseURI + $modelLink.href 213 | 214 | # request the specific model webpage 215 | $modelIndex = Invoke-WebRequest -Uri $modelURI 216 | 217 | Write-Output "Requesting available downloads for model $model" 218 | 219 | # get webpage elements for the desired section ID 220 | $sectionIndex = $modelIndex.ParsedHtml.getElementsByTagName('DIV') | Where-Object {$_.id -eq $sectionID} 221 | 222 | # get webpage elements for the section rows 223 | $sectionRows = $sectionIndex.getElementsByTagName('TR') 224 | 225 | # initialize an empty array to store section results 226 | $sectionResults = @() 227 | 228 | # loop through each section row (skipping the first which only contains known header values) 229 | for ($secCounter = 1; $secCounter -lt ($sectionRows | Measure-Object).Count; $secCounter++) 230 | { 231 | # get webpage elements for the row cells 232 | $sectionCells = $sectionRows[$secCounter].getElementsByTagName('TD') 233 | 234 | # loop through each row cell 235 | for ($cellCounter = 0; $cellCounter -lt ($sectionCells | Measure-Object).Count; $cellCounter++) 236 | { 237 | # set Download cell value(s) 238 | if ($cellCounter -eq 5) 239 | { 240 | # get hyperlink webpage elements for the download cell 241 | $cellLinks = $sectionCells[$cellCounter].getElementsByTagName('A') 242 | 243 | # get the download links and change them to https (seems to work better for actual downloading) 244 | $dlLinks = ($cellLinks | Select-Object -ExpandProperty href) -replace 'http://','https://' 245 | 246 | if ($dlLinks.Count -gt 1) 247 | { 248 | # for cells with multiple links, convert array to single string with newlines. 249 | # this allows the final results to display like the other cells 250 | $dlLinks = ($dlLinks -join [Environment]::NewLine | Out-String).TrimEnd() 251 | } 252 | } 253 | else 254 | { 255 | # set other cell values 256 | switch ($cellCounter) 257 | { 258 | '0' {$DescriptionText = $sectionCells[$cellCounter].innerText} 259 | '1' {$Importance = $sectionCells[$cellCounter].innerText} 260 | '2' {$Version = $sectionCells[$cellCounter].innerText} 261 | '3' {$Released = ($sectionCells[$cellCounter].innerText | Get-Date)} 262 | '4' {$SupportedOS = $sectionCells[$cellCounter].innerText} 263 | } 264 | } 265 | } 266 | 267 | # add cell values for each row to the section results array 268 | $sectionResults += New-Object psobject -Property @{Description=$DescriptionText; 269 | Importance=$Importance; 270 | Version=$Version; 271 | Released=$Released; 272 | SupportedOS=$SupportedOS; 273 | Download=$dlLinks} 274 | } 275 | 276 | # if more than one model, get only the most recent release(s) for each model 277 | if ($models.Count -gt 1) 278 | { 279 | # set variable for the latest date found in the section results array 280 | $latestDate = ($sectionResults.Released | Measure-Object -Maximum).Maximum 281 | 282 | # set variable for the latest release(s) found that match(es) the latest date 283 | $latestRelease = $sectionResults | Where-Object {$_.Released -eq $latestDate} 284 | 285 | foreach ($release in $latestRelease) 286 | { 287 | # add the latest release row(s) to the model results array 288 | $modelResults += New-Object psobject -Property @{Model=$model; 289 | Description=$release.Description; 290 | Importance=$release.Importance; 291 | Version=$release.Version; 292 | Released=$release.Released; 293 | SupportedOS=$release.SupportedOS; 294 | Download=$release.Download} 295 | } 296 | } 297 | # else get all releases for the single model 298 | else 299 | { 300 | foreach ($sectionItem in $sectionResults) 301 | { 302 | # add the release rows to the model results array 303 | $modelResults += New-Object psobject -Property @{Model=$model; 304 | Description=$sectionItem.Description; 305 | Importance=$sectionItem.Importance; 306 | Version=$sectionItem.Version; 307 | Released=$sectionItem.Released; 308 | SupportedOS=$sectionItem.SupportedOS; 309 | Download=$sectionItem.Download} 310 | } 311 | } 312 | } 313 | 314 | # sort results by date 315 | $sortedResults = $modelResults | Sort-Object -Property Released -Descending 316 | 317 | # change the Released datetimes to short date strings so the unnecessary time part doesn't display 318 | $sortedResults | ForEach-Object {$_.Released = $_.Released.ToShortDateString()} 319 | 320 | # define desired properties to display 321 | if ($IncludeDescription) {$properties = 'Model','Description','Released','Version','SupportedOS','Download'} 322 | else {$properties = 'Model','Released','Version','SupportedOS','Download'} 323 | 324 | # if the download location parameter has been used, allow passthru selection of desired downloads 325 | if ($DownloadLocation) 326 | { 327 | # display results in a grid view and set selected downloads variable to the user's selection 328 | $selectedDownloads = $sortedResults | Select-Object -Property $properties | Out-GridView -PassThru | Select-Object -ExpandProperty Download 329 | 330 | foreach ($selection in $selectedDownloads) 331 | { 332 | # split download selections that have more than one line into array 333 | $downloads = $selection -split [System.Environment]::NewLine 334 | 335 | foreach ($download in $downloads) 336 | { 337 | # get the filename substring 338 | $charIndex = ($download.LastIndexOf('/')) + 1 339 | $fileName = $download.Substring($charIndex,($download.Length) - $charIndex) 340 | 341 | # download the file 342 | Invoke-WebRequest -Uri $download -OutFile $DownloadLocation\$filename 343 | } 344 | } 345 | } 346 | # else just display the results 347 | else 348 | { 349 | $sortedResults | Select-Object -Property $properties | Out-GridView 350 | } 351 | } --------------------------------------------------------------------------------