├── Intune_Win32 ├── IntuneWinAppUtil.exe ├── Microsoft License Terms For Win32 Content Prep Tool.pdf ├── README.md └── ReleaseNotes.txt ├── LICENSE ├── PSAPP_GUI.ps1 ├── README.md ├── Toolkit ├── AppDeployToolkit │ ├── AppDeployToolkitBanner.png │ ├── AppDeployToolkitConfig.xml │ ├── AppDeployToolkitExtensions.ps1 │ ├── AppDeployToolkitHelp.ps1 │ ├── AppDeployToolkitLogo.ico │ ├── AppDeployToolkitMain.cs │ └── AppDeployToolkitMain.ps1 ├── Deploy-Application.exe ├── Deploy-Application.exe.config └── Deploy-Application.ps1 └── config ├── Deploy-Application.ps1 ├── MainWindow.xaml ├── OSCCLogo.jpg ├── Prefs.xml └── swid.xml /Intune_Win32/IntuneWinAppUtil.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Intune_Win32/IntuneWinAppUtil.exe -------------------------------------------------------------------------------- /Intune_Win32/Microsoft License Terms For Win32 Content Prep Tool.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Intune_Win32/Microsoft License Terms For Win32 Content Prep Tool.pdf -------------------------------------------------------------------------------- /Intune_Win32/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft Win32 Content Prep Tool 2 | Manage Windows Apps (.intunewin) with Intune 3 | 4 | [Version 1.6](https://github.com/Microsoft/Microsoft-Win32-Content-Prep-Tool/releases/tag/v1.6) 5 | 6 | [See release notes for more information.](https://github.com/Microsoft/Microsoft-Win32-Content-Prep-Tool/releases) 7 | 8 | Use the Microsoft Win32 Content Prep Tool to pre-process Windows Classic apps. The packaging tool converts application installation files into the .intunewin format. The packaging tool also detects the parameters required by Intune to determine the application installation state. After you use this tool on your apps, you will be able to upload and assign the apps in the Microsoft Intune console. 9 | 10 | Before you install and the use Microsoft Win32 Content Prep Tool you **must**: 11 | * Review the [Microsoft License Terms for Microsoft Win32 Content Prep Tool](https://github.com/Microsoft/Microsoft-Win32-Content-Prep-Tool/blob/master/Microsoft%20License%20Terms%20For%20Win32%20Content%20Prep%20Tool.pdf). Print and retain a copy of the license terms for your records. By downloading and using Microsoft Win32 Content Prep Tool, you agree to such license terms. If you do not accept them, do not use the software. 12 | * Review the [Microsoft Intune Privacy Statement](https://docs.microsoft.com/legal/intune/microsoft-intune-privacy-statement) for information on the privacy policy of the Microsoft Win32 Cotnent Prep Tool. 13 | 14 | Sample commands to use for the Microsoft Win32 Content Prep Tool: 15 | * IntuneWinAppUtil -h 16 | * This will show usage information for the tool. 17 | * IntuneWinAppUtil -c -s -o <-q> 18 | * This will generate the .intunewin file from the specified source folder and setup file. 19 | * For MSI setup file, this tool will retrieve required information for Intune. 20 | * If -a is specified, all catalog files in that folder will be bundled into the .intunewin file. 21 | * If -q is specified, it will be in quiet mode. If the output file already exists, it will be overwritten. 22 | * Also if the output folder does not exist, it will be created automatically. 23 | * IntuneWinAppUtil 24 | * If no parameter is specified, this tool will guide you to input the required parameters step by step. 25 | 26 | Command-line parameters available 27 | * -h Help 28 | * -c Setup folder for all setup files. All files in this folder will be compressed into .intunewin file. 29 | * Only the setup files for this app should be in this folder. 30 | * -s Setup file (e.g. setup.exe or setup.msi). 31 | * -o Output folder for the generated .intunewin file. 32 | * -a Catalog folder for all catalog files. All files in this folder will be treated as catalog file for Win10 S mode. 33 | 34 | **Note: The generated .intunewin file contains all compressed and encrypted source setup files and the encryption information to decrypt it. Please keep it in the safe place as your source setup files.** 35 | -------------------------------------------------------------------------------- /Intune_Win32/ReleaseNotes.txt: -------------------------------------------------------------------------------- 1 | 1.4 2 | Microsoft Intune Win32 App Packaging Tool will check the Windows Classic setup files and generate a .intunewin file to be imported into Intune Azure Portal. 3 | 4 | 1.5 5 | Fixed the error when processing .msp file as setup file. 6 | 7 | 1.6 8 | Add catalog support for Win10 S. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TomDegreef 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PSAPP_GUI.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This GUI allows for (basic) easy creation of PsAppDeploymentToolkit packages. 4 | .DESCRIPTION 5 | - Fully unattended generation of the Deploy-Application.PS1 file 6 | - Merging of your source binaries with the PS App toolkit on a destination Share (must be UNC) 7 | - Creating the SCCM Application + Deployment Type 8 | - Adding a Software ID tag and using this as a detection mechanism. 9 | .NOTES 10 | FileName: PSAPP_GUI.ps1 11 | Blog: http://www.OSCC.Be 12 | Author: Tom Degreef 13 | Twitter: @TomDegreef 14 | Email: Tom.Degreef@OSCC.Be 15 | Created: 2017-08-23 16 | Updated: 2021-12-10 17 | 18 | Version history 19 | 1.5 - (2021-12-10) Hide PowerShell console window, add feature to get language from MSI file, remove swid lookup, update XAML, add scrollbars 20 | 1.4 - Default DT is now install for system, fixed uninstall but where swidtag wasn't removed properly if there was a space in the appname, Creates intune W32 packages, Browse for MSI,EXE files, Whois lookup fixed 21 | 1.3 - (2018-02-06) Created Preferences TAB with automatic actions for SCCM, Specify your own limiting collection, create security groups in AD and link them to collections 22 | 1.2 - (2017-09-27) SWID-Tags are Cached and will be pre-filled out if vendor is used again, Source & Destination path are cached, removed bug in detection method, added listbox for uninstall functionality 23 | 1.1 - (2017-09-06) Experimental support for SWID-Tag lookup is added 24 | 1.0 - (2017-09-05) Added 2nd Tab for SCCM, Allowing software distribution & Collection Creation 25 | 0.9 - (2017-08-23) Initial version of the GUI 26 | .LINK 27 | Http://www.OSCC.Be 28 | #> 29 | 30 | $gui_version = '1.5' 31 | $logfile = "$env:systemdrive\temp\PSAPPgui\PSAPP_Gui.txt" 32 | $logmodule = "$env:systemdrive\temp\PSAPPgui\PSAPP_Gui.log" 33 | $global:packagepath = "" 34 | $global:architecture = "" 35 | $global:fullname="" 36 | $global:Sitecode="" 37 | $global:ADEnabled=$False 38 | $global:dest="" 39 | $global:detectionscript="" 40 | $global:Startlocation=get-location 41 | $global:intunedection="" 42 | 43 | $logopath = "$psscriptroot\config\OSCCLogo.jpg" 44 | 45 | #Check if OSCC Logging module is installed, if not, install it 46 | if ($PSVersionTable.PSVersion -ge '5.0.0.0') 47 | { 48 | if (!(Get-PackageProvider | Where-Object {$_.Name -eq 'NuGet'})) 49 | { Install-PackageProvider -Name Nuget -force -MinimumVersion 2.8.5.208 } 50 | if (!(get-module -ListAvailable -Name osccpslogging | Where-Object {$_.Version -ge '1.5.0.1'})) 51 | { 52 | write-host "installing module" 53 | install-module -Name OSCCPsLogging -MinimumVersion 1.5.0.1 -Force} 54 | } 55 | 56 | if (!(test-path "$env:systemdrive\temp\PSAPPgui")) 57 | { 58 | New-Item -itemtype Directory -path $env:systemdrive\temp\PSAPPgui -Force 59 | } 60 | 61 | try {$log = Initialize-CMLogging -LogFileName $logmodule -OMSFileLogLevel off -ConsoleLogLevel off -fileLogLevel info} catch {} 62 | function log-item 63 | { 64 | param( $logline, 65 | $severity = "Info" ) 66 | [string]$currentdatetime = get-date 67 | if (get-module -name OSCCPSLogging) 68 | { 69 | Switch ($severity) 70 | { 71 | "Info" {$log.Info($logline)} 72 | "Warn" {$log.Warn($logline)} 73 | "Error" {$log.Error($logline)} 74 | } 75 | 76 | } 77 | else 78 | { 79 | Switch ($severity) 80 | { 81 | "Info" {[string]$output = $currentdatetime + " - INFO - " + $logline} 82 | "Warn" {[string]$output = $currentdatetime + " - WARNING - " + $logline} 83 | "Error" {[string]$output = $currentdatetime + " - ERROR - " + $logline} 84 | } 85 | $output | out-file $Logfile -append 86 | } 87 | } 88 | 89 | # Hides the PowerShell console window 90 | powershell.exe -WindowStyle Hidden -Command "" 91 | 92 | Log-Item -logline "Starting Powershell AppDeployment Toolkit GUI version $gui_version" 93 | 94 | try 95 | { 96 | $inputXML = Get-Content "$psscriptroot\config\MainWindow.xaml" -ErrorAction stop 97 | $inputXML = $inputXML -replace "ReplacePath", $logopath 98 | log-item("GUI XML sucessfully loaded") 99 | } 100 | catch 101 | { 102 | log-item -logline "error loading GUI XML" -severity "Error" 103 | log-item -logline "exiting script" -severity "Error" 104 | exit 105 | } 106 | 107 | try { 108 | $inputApptoolkit = get-content "$psscriptroot\config\Deploy-Application.ps1" -ErrorAction stop 109 | log-item("'Powershell AppDeployment Toolkit' input-file succesfully loaded") 110 | } 111 | catch { 112 | log-item -logline "error loading 'Powershell AppDeployment Toolkit' input-file " -severity "Error" 113 | log-item -logline "exiting script" -severity "Error" 114 | exit 115 | } 116 | 117 | try { 118 | $Preferences = New-Object -TypeName XML 119 | $Preferences.load("$psscriptroot\config\Prefs.xml") 120 | log-item("Loading preferences") 121 | } 122 | catch { 123 | log-item -logline "Failed loading preferences " -severity "Error" 124 | log-item -logline "Continuing script but without preferences" -severity "Warn" 125 | } 126 | 127 | try { 128 | $SWIDtags = New-Object -TypeName XML 129 | $SWIDtags.load("$psscriptroot\config\swid.xml") 130 | log-item("Loading SWIDtag cache") 131 | } 132 | catch { 133 | log-item -logline "Failed loading SWIDtag cache" -severity "Error" 134 | log-item -logline "Continuing script but without SWIDtag cache" -severity "Warn" 135 |  Log-Item -logline "Caught an exception:" -severity "Error" 136 |     log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 137 |     log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 138 | } 139 | 140 | Function Prefill{ 141 | 142 | $wpfCB_SourcePath.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.settings.Save_SP) 143 | $wpfCB_DestinationPath.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.Settings.Save_DP) 144 | $wpfCB_SiteCode.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.Settings.Save_SiteCode) 145 | $wpfCB_DP.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.Settings.Save_DistPoint) 146 | $wpfCB_DPG.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.Settings.Save_DistGroup) 147 | 148 | #If no sitecode exist in preference, disable autoconnect untill manual connection is made first and the sitecode is saved in preferences 149 | if ($Preferences.Preferences.SiteCode.SC -ne "") 150 | { 151 | log-item -logline "Sitecode is available in preferences, enabling automatic SCCM functionality" 152 | $wpfCB_Auto_Sccm.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.SCCM_Prefs.Autoconnect) 153 | $wpfCB_Auto_Import.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.SCCM_Prefs.AutoImport) 154 | $wpfCB_Auto_Collection.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.SCCM_Prefs.AutoCollection) 155 | Try { Import-Module ActiveDirectory -ErrorAction Stop 156 | $wpfCB_Auto_ADGroup.IsEnabled = $true 157 | $wpfTB_LDAP.IsEnabled = $true 158 | #$wpfCB_CreateAD.IsEnabled = $true 159 | log-item -logline "Active Directory PS Module found, enabling AD Functionality" 160 | $wpfCB_Auto_ADGroup.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.SCCM_Prefs.AutoADGroup) 161 | #if ($wpfCB_Auto_ADGroup.IsChecked = $True) 162 | # { 163 | # $wpfCB_CreateAD.IsChecked = $True 164 | # } 165 | } 166 | catch { 167 | 168 | log-item -logline "Active Directory PS Module NOT found, Disabling AD Functionality" -severity warn 169 | $wpfCB_Auto_ADGroup.IsEnabled = $False 170 | $wpfTB_LDAP.IsEnabled = $false 171 | # $wpfCB_CreateAD.IsEnabled = $False 172 | } 173 | } 174 | else { 175 | log-item -logline "Sitecode is NOT available in preferences, disabling automatic SCCM functionality" -severity warn 176 | $wpfTB_AutoPerform.Content = "Automatic SCCM actions below are not available until a package is manually imported through the GUI interface" 177 | $wpfTB_AutoPerform.Foreground = "red" 178 | $wpfCB_Auto_Sccm.IsEnabled = $false 179 | $wpfCB_Auto_Import.IsEnabled = $false 180 | $wpfCB_Auto_Collection.IsEnabled = $false 181 | #$wpfCB_Auto_Sccm.tooltipservice = $true 182 | #$wpfCB_Auto_Sccm.tooltip = "Setting is currently disabled because SCCM details are unknown. After creating a package, connect to SCCM manually from the app" 183 | #$wpfCB_Auto_Import.tooltip = "Setting is currently disabled because SCCM details are unknown. After creating a package, connect to SCCM manually from the app" 184 | $wpfCB_Auto_ADGroup.IsEnabled = $False 185 | $wpfTB_LDAP.IsEnabled = $false 186 | #$wpfCB_CreateAD.IsEnabled = $False 187 | } 188 | 189 | if (($Preferences.Preferences.SelectedDP.DP.Count -gt 1) -or ($Preferences.Preferences.SelectedDPGroup.Dpg.count -gt 1)) 190 | { 191 | log-item -logline "At least 1 DP or DP Group was used before, enabling Auto-distribution" 192 | $wpfCB_Auto_Distribute.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.SCCM_Prefs.AutoDistribute) 193 | } 194 | else { 195 | log-item -logline "No DP or DP Group is known, disabling Auto-distribution" 196 | $wpfCB_Auto_Distribute.isenabled = $false 197 | } 198 | 199 | $wpfCB_Intunepackage.IsChecked = [System.Convert]::ToBoolean($Preferences.Preferences.Settings.IntunePackage) 200 | $wpfTB_DefaultLimiting.Text = $Preferences.Preferences.SCCM_Prefs.LimitingCollection 201 | $wpfTB_LDAP.Text = $Preferences.Preferences.SCCM_Prefs.LDAP 202 | $wpfprefix.text = $Preferences.Preferences.Collection.Prefix 203 | $wpfsuffix.text = $Preferences.Preferences.Collection.Suffix 204 | $WPFTB_IntuneFolder.Text = $Preferences.Preferences.SCCM_Prefs.IntuneAppfolder 205 | 206 | if ($wpfCB_Auto_ADGroup.IsChecked -and $wpfCB_Auto_ADGroup.IsEnabled) 207 | { 208 | # $wpfCB_CreateAD.Ischecked = $True 209 | } 210 | 211 | if ($wpfCB_SourcePath.IsChecked -and $Preferences.Preferences.Sourcepath.SP -ne "") 212 | { 213 | $wpfinputSource.text = $Preferences.Preferences.Sourcepath.sp 214 | $wpfinputSource.Tag = 'cleared' 215 | } 216 | if ($wpfCB_DestinationPath.IsChecked -and $Preferences.Preferences.Destinationpath.DP -ne "") 217 | { 218 | $wpfInputDestination.text = $Preferences.Preferences.Destinationpath.DP 219 | $wpfInputDestination.Tag = 'cleared' 220 | } 221 | if ($Preferences.Preferences.EndMessage.EM -ne "") 222 | { 223 | $wpfTB_EndMessage.Text = $Preferences.Preferences.EndMessage.EM 224 | } 225 | if (-not ($wpfCB_Intunepackage.IsChecked)) 226 | { 227 | $WPFTB_IntuneFolder.IsEnabled = $False 228 | } 229 | log-item -logline "Updated GUI with preset preferences" 230 | } 231 | 232 | Function Update_Prefs{ 233 | param($sp,$dp,$em) 234 | 235 | If ($wpfCB_SourcePath.IsChecked) 236 | { 237 | $item = Select-XML -Xml $Preferences -XPath '//Sourcepath[1]' 238 | $item.Node.SP = $sp 239 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 240 | } 241 | if ($wpfCB_DestinationPath.IsChecked) 242 | { 243 | $item = Select-XML -Xml $Preferences -XPath '//Destinationpath[1]' 244 | $item.Node.DP = $dp 245 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 246 | } 247 | if ($em -ne "") 248 | { 249 | $item = Select-XML -Xml $Preferences -XPath '//EndMessage[1]' 250 | $item.Node.EM = $em 251 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 252 | } 253 | 254 | } 255 | function Check_and_Save_xml 256 | { Param($name,$year,$month,$domain) 257 | 258 | $output = $SWIDtags.swid.RegId.Vendor | Where-object {$_.Name -eq $Name} | Select-Object -Property Name,Year,month,domain 259 | if ($output.name -ne $wpfVendor.text) 260 | { log-item -logline ("Adding RegID information to preferences file for vendor : " + $name) 261 | $item = Select-XML -Xml $SWIDtags -XPath '//Vendor[1]' 262 | $newnode = $item.Node.CloneNode($true) 263 | $newnode.Name = $Name 264 | $newnode.Year = $Year 265 | $newnode.Month = $Month 266 | $newnode.Domain = $Domain 267 | $Vendor = Select-XML -Xml $SWIDtags -XPath '//RegId' 268 | $Vendor.Node.AppendChild($newnode) 269 | $SWIDtags.Save("$psscriptroot\config\swid.xml") 270 | } 271 | else { 272 | log-item -logline ("fRegID information was already available in swid.xml") 273 | } 274 | } 275 | 276 | Function Fill-swid 277 | { 278 | log-item -logline ("Checking if vendor information is availablein swid.xml") 279 | if ($wpfvendor.text -ne '') 280 | { 281 | $output = $SWIDtags.swid.RegId.Vendor | Where-object {$_.Name -eq $wpfVendor.text} | Select-Object -Property Name,Year,month,domain 282 | if ($output.name -ne $wpfVendor.text ) 283 | { log-item -logline ($wpfVendor.text + " not found as a vendor in preferences file")} 284 | else { 285 | log-item -logline ($wpfVendor.text + " was found as a vendor in preferences file. Populating fields.") 286 | $wpfregidYY.text = $output.year 287 | $wpfregidMM.text = $output.month 288 | $wpfregidDomain.text = $output.domain 289 | $wpfregidyy.background = "green" 290 | $wpfregidmm.background = "green" 291 | $wpfregidDomain.background = "green" 292 | $wpfregidYY.Tag = 'cleared' 293 | $wpfregidMM.Tag = 'cleared' 294 | $wpfregidDomain.Tag = 'cleared' 295 | 296 | } 297 | } 298 | } 299 | 300 | Function Compose-Package 301 | { 302 | Param($source,$destination,$appvendor,$appname,$appversion,$appbitness) 303 | 304 | Switch ($appbitness) 305 | { 306 | "0" 307 | { 308 | $global:architecture = "X86" 309 | } 310 | "1" 311 | { 312 | $global:architecture = "X64" 313 | } 314 | } 315 | 316 | if ($destination.Endswith("\")) 317 | { 318 | $global:packagepath = $destination + $appvendor + "\" + $appname + "_" + $appversion + "_" + $global:architecture 319 | } 320 | else 321 | { 322 | $global:packagepath = $destination + "\" + $appvendor + "\" + $appname + "_" + $appversion + "_" + $global:architecture 323 | } 324 | 325 | $global:dest=$destination 326 | 327 | $pct = 0 328 | 329 | new-item -itemtype Directory -path $packagepath -force 330 | 331 | #$WPFPackage_progress.Content = "Copying toolkit to $packagepath" 332 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPackage_progress.Content = "Copying toolkit to $packagepath"} ),$null, $null ) 333 | 334 | Copy-item -path "$psscriptroot\Toolkit\*" -Destination $packagepath -Recurse -Force 335 | log-item ("Copying toolkit to $packagepath") 336 | 337 | if (-not (test-path "$packagepath\Files")) { 338 | Log-Item "Subfolder Files did not exist in toolkit, creating it" 339 | New-Item -ItemType Container -Path "$packagepath\Files" -force 340 | } 341 | 342 | if (-not (test-path "$packagepath\SupportFiles")) { 343 | Log-Item "Subfolder SupportFiles did not exist in toolkit, creating it" 344 | New-Item -ItemType Container -Path "$packagepath\SupportFiles" -force 345 | } 346 | 347 | $sourcefiles = Get-ChildItem $source -Recurse -Force 348 | 349 | [float]$blocks = (100 / $sourcefiles.Count) 350 | foreach ($file in $sourcefiles) 351 | { 352 | $size = "{0:N2}" -f (($file.Length)/1MB) 353 | $dest = ($file.FullName -replace [regex]::Escape($source), "$packagepath\Files") 354 | 355 | #$WPFPackage_progress.Content = "Copying file $file with size $size MB" 356 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPackage_progress.Content = "Copying file $file with size $size MB"} ),$null, $null ) 357 | 358 | if ($file.PSIsContainer) 359 | { 360 | $null = New-Item -ItemType Container -Path $dest -force 361 | log-item ("Created folder $dest") 362 | } 363 | if (-not $file.psiscontainer) 364 | { 365 | Copy-Item $file.fullname -Force -Destination $dest 366 | log-item ("Copying $file ($size MB)") 367 | } 368 | 369 | $pct += $blocks 370 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $wpfprogress.Value = $pct } ),$null, $null ) 371 | 372 | } 373 | 374 | } 375 | 376 | Function Invoke-MemberOnType #Used in the extraction of MSI properties 377 | { 378 | param 379 | ( 380 | #The string containing the name of the method to invoke 381 | [Parameter(Mandatory=$true, Position = 0)] 382 | [ValidateNotNullOrEmpty()] 383 | [String]$Name, 384 | 385 | #A bitmask comprised of one or more BindingFlags that specify how the search is conducted. 386 | #The access can be one of the BindingFlags such as Public, NonPublic, Private, InvokeMethod, GetField, and so on 387 | [Parameter(Mandatory=$false, Position = 3)] 388 | [System.Reflection.BindingFlags]$InvokeAttr = "InvokeMethod", 389 | 390 | #The object on which to invoke the specified member 391 | [Parameter(Mandatory=$true, Position = 1)] 392 | [ValidateNotNull()] 393 | [Object]$Target, 394 | 395 | #An array containing the arguments to pass to the member to invoke. 396 | [Parameter(Mandatory=$false, Position = 2)] 397 | [Object[]]$Arguments = $null 398 | ) 399 | 400 | #Invokes the specified member, using the specified binding constraints and matching the specified argument list. 401 | 402 | $Target.GetType().InvokeMember($Name,$InvokeAttr,$null,$Target,$Arguments) 403 | } 404 | 405 | Function Get-MSIInfo { 406 | [CmdletBinding()] 407 | param ( 408 | [Parameter(Position=0,Mandatory=$true)] 409 | [string] 410 | $fullfilename, 411 | [Parameter(Position=1)] 412 | [Switch] 413 | $Overwrite 414 | ) 415 | $Extension = [System.IO.Path]::GetExtension($fullfilename) 416 | switch ($extension) { 417 | ".msi" { 418 | Log-Item "MSI file chosen" 419 | 420 | # Get data from the MSI DB 421 | $type = [Type]::GetTypeFromProgID("WindowsInstaller.Installer") 422 | $installer = [Activator]::CreateInstance($type) 423 | $db = Invoke-MemberOnType "OpenDatabase" $installer @($fullfilename,0) 424 | $view = Invoke-MemberOnType "OpenView" $db ('SELECT * FROM Property') 425 | Invoke-MemberOnType "Execute" $view $null 426 | $record = Invoke-MemberOnType "Fetch" $view $null 427 | 428 | while ($null -ne $record) 429 | { 430 | $property = Invoke-MemberOnType "StringData" $record 1 "GetProperty" 431 | switch ($property){ 432 | "ProductCode" { 433 | $value = Invoke-MemberOnType "StringData" $record 2 "GetProperty" 434 | Log-Item -logline "$property = $value" 435 | if ($Overwrite -eq $true -or [string]::IsNullorWhiteSpace($WPFUninstallCmd.text)) { 436 | $WPFUninstallCmd.text = $value.Trim() 437 | } 438 | $wpfUnInstallType.SelectedIndex = 2 439 | } 440 | "ProductVersion" { 441 | $value = Invoke-MemberOnType "StringData" $record 2 "GetProperty" 442 | Log-Item -logline "$property = $value" 443 | if ($Overwrite -eq $true -or [string]::IsNullorWhiteSpace($WPFAppversion.text)) { 444 | $WPFAppversion.text = $value.Trim() 445 | } 446 | 447 | } 448 | "ProductName" { 449 | $value = Invoke-MemberOnType "StringData" $record 2 "GetProperty" 450 | Log-Item -logline "$property = $value" 451 | if ($Overwrite -eq $true -or [string]::IsNullorWhiteSpace($wpfappname.text)) { 452 | $wpfappname.text = $value.Trim() 453 | } 454 | } 455 | "Manufacturer" { 456 | $value = Invoke-MemberOnType "StringData" $record 2 "GetProperty" 457 | Log-Item -logline "$property = $value" 458 | if ($Overwrite -eq $true -or [string]::IsNullorWhiteSpace($wpfvendor.text)) { 459 | $wpfvendor.text = $value.Trim() 460 | } 461 | } 462 | "ProductLanguage"{ 463 | $value = Invoke-MemberOnType "StringData" $record 2 "GetProperty" 464 | Log-Item -logline "$property = $value" 465 | if ($Overwrite -eq $true -or [string]::IsNullorWhiteSpace($wpfapplanguage.text)) { 466 | $wpfapplanguage.text = ([System.Globalization.CultureInfo]::GetCultureInfo([int]$value)).DisplayName 467 | } 468 | } 469 | } 470 | $record = Invoke-MemberOnType "Fetch" $view $null 471 | } 472 | $wpfinstalltype.SelectedIndex = 0 473 | } 474 | 475 | ".exe" { 476 | Log-Item "EXE file chosen" 477 | $FullVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullfilename).ProductVersion 478 | $ProductLanguage = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullfilename).Language 479 | $Manufacturer = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullfilename).CompanyName 480 | $ProductName = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullfilename).ProductName 481 | Log-item "Version : $FullVersion" 482 | $WPFAppversion.text = $FullVersion.Trim() 483 | Log-Item "Language : $ProductLanguage" 484 | Log-Item "Vendor : $Manufacturer" 485 | $wpfvendor.text = $Manufacturer.Trim() 486 | Log-Item "Name : $ProductName" 487 | $wpfappname.text = $productname.Trim() 488 | $wpfinstalltype.SelectedIndex = 1 489 | $wpfUnInstallType.SelectedIndex = 1 490 | } 491 | } 492 | } 493 | 494 | Function File-Picker 495 | { 496 | $openFileDialog = New-Object system.windows.forms.openfiledialog 497 | 498 | if ($wpfCB_SourcePath.IsChecked -and $Preferences.Preferences.Sourcepath.SP -ne "") 499 | { 500 | $openFileDialog.initialDirectory = $wpfinputSource.text 501 | } 502 | else { 503 | $openFileDialog.initialDirectory = [System.IO.Directory]::GetCurrentDirectory() 504 | } 505 | $openFileDialog.title = "Select Executable or MSI" 506 | $openFileDialog.filter = "Installable Files (*.exe;*.msi)|*.exe;*.msi|All files (*.*)|*.*" 507 | $result = $openFileDialog.ShowDialog((New-Object System.Windows.Forms.Form -Property @{TopMost = $true })) 508 | 509 | if($result -eq "OK") 510 | { 511 | $fullfilename = $OpenFileDialog.filename 512 | $filepath = split-path -path $fullfilename 513 | $Filename = split-path -path $fullfilename -Leaf 514 | Log-Item -logline "Filebrowser results :" 515 | Log-Item -Logline "selected path = $filepath" 516 | Log-Item -Logline "selected filename = $filename" 517 | 518 | Log-Item -Logline "Clearing all entered fields on Package Tab" 519 | $wpfappname.text = "" 520 | $wpfappname.background = "white" 521 | $wpfvendor.text = "" 522 | $wpfvendor.Background = "white" 523 | $WPFAppversion.Text = "" 524 | $WPFAppversion.Background = "white" 525 | $WPFAppLanguage.Text = "" 526 | $WPFAppLanguage.Background = "white" 527 | $WPFAppRevision.Text = "" 528 | $WPFAppRevision.Background = "white" 529 | $wpfregidYY.Text = "YYYY" 530 | $wpfregidYY.Background = "white" 531 | $wpfregidMM.Text = "MM" 532 | $wpfregidMM.Background = "white" 533 | $wpfregidDomain.Text = "Com.Domainname" 534 | $wpfregidDomain.Background = "white" 535 | $WPFLicense.SelectedIndex = "-1" 536 | $WPFLicenseText.Background = "white" 537 | $wpfinputsource.Background = "white" 538 | $wpfinputdestination.Background = "white" 539 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPackage_progress.Content = ""} ),$null, $null ) 540 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $wpfprogress.value = 0 } ),$null, $null ) 541 | 542 | Log-Item -Logline "Clearing all entered fields on SCCM Tab" 543 | $WPFsitecode.content = "" 544 | $WPFsitecode.Background = "white" 545 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = ""} ),$null, $null ) 546 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 0 } ),$null, $null ) 547 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfdistribute_progress.Content = ""} ),$null, $null ) 548 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFPB_Progress_distribute.value = 0 } ),$null, $null ) 549 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfsccm.background = "white" } ),$null, $null ) 550 | $wpfsccm.IsEnabled = $false 551 | $wpfdp.items.Clear() 552 | $wpfdpgroup.items.Clear() 553 | $WPFCollections_info.Background = "white" 554 | $WPFCollections_info.Content = "" 555 | 556 | Get-MSIInfo -fullfilename $fullfilename -Overwrite 557 | } 558 | else { Log-Item -Logline "File-browser Cancelled!"} 559 | 560 | $wpfinputsource.text = $filepath 561 | $wpfinstallpath.text = $filename 562 | $wpfinputSource.Tag = 'cleared' #if not cleared, will clear input source on click. (reported by Sandy) 563 | 564 | Fill-swid 565 | 566 | } 567 | Function Execute-Scriptblock 568 | { 569 | Param($Remote,$SB, $sess) 570 | 571 | switch ($remote) 572 | { 573 | "Succeeded" 574 | { 575 | write-host "succeded part" 576 | Invoke-Command -Session $sess -ScriptBlock $SB 577 | } 578 | "Failed" 579 | { 580 | log-item "Remote connection failed, not executing current scriptblock" -severity warn 581 | } 582 | "Local" 583 | { 584 | write-host "local part" 585 | Invoke-Command -ScriptBlock $SB 586 | } 587 | } 588 | } 589 | 590 | Function Connect-SCCM 591 | { 592 | $Connection = $False 593 | 594 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Connecting to SCCM Environment ..."} ),$null, $null ) 595 | 596 | Try 597 | { 598 | Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" -ErrorAction Stop # Import the ConfigurationManager.psd1 module 599 | $global:Sitecode = (Get-PSDrive -PSProvider CMSite).Name 600 | log-item ("SCCM Sitecode = $Sitecode") 601 | Set-Location ($global:Sitecode + ":") # Set the current location to be the site code. 602 | #$WPFsitecode.Content = "Connected to Site : " + $global:Sitecode 603 | $wpfSiteCode.background = "Green" 604 | $Connection = $True 605 | $WPFImport_SCCM.isenabled = $true 606 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFsitecode.Content = "Connected to Site : " + $global:Sitecode} ),$null, $null ) 607 | 608 | if ($wpfCB_SiteCode.IsChecked) 609 | { 610 | $item = Select-XML -Xml $Preferences -XPath '//SiteCode[1]' 611 | $item.Node.SC = $global:Sitecode 612 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 613 | log-item "Updated Sitecode in Preferences XML" 614 | } 615 | 616 | } 617 | Catch 618 | { 619 | log-item ("Failed connecting to the SCCM Environment") -severity "Error" 620 | #$wpfImport_progress.Content = "Failed connecting to the SCCM Environment" 621 | $WPFConnect_SCCM.Background="Red" 622 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFsitecode.Content = "Failed connecting to the SCCM Environment"} ),$null, $null ) 623 | # $WPFProgress_SCCM.Foreground="Red" 624 | # $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 100; } ),$null, $null ) 625 | } 626 | 627 | if ($connection) 628 | { 629 | $wpfdp.items.Clear() 630 | $wpfdpgroup.items.Clear() 631 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Enumerating Distribution Points"} ),$null, $null ) 632 | $DPs = get-cmdistributionpoint -allsite 633 | foreach ($dp in $dps) 634 | { 635 | $wpfdp.Items.Add($dp.NetworkOsPath.trimstart("\")) 636 | } 637 | $DPGs = Get-CMDistributionPointGroup 638 | foreach ($dpg in $dpgs) 639 | { 640 | $wpfdpgroup.items.add($dpg.name) 641 | } 642 | #$wpfImport_progress.Content = "Distribution Points enumerated from site" 643 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Distribution Points enumerated from site"} ),$null, $null ) 644 | 645 | if ($wpfCB_Auto_Import.IsChecked) 646 | { 647 | Import-SCCM 648 | } 649 | if ($wpfCB_Auto_Collection.IsChecked) 650 | { 651 | Generate-Collections 652 | } 653 | } 654 | } 655 | 656 | Function Generate-Detection{ 657 | 658 | $appwithoutspaces = $wpfappname.text -replace " ","_" 659 | $appversionwithoutspaces = $WPFappVersion.text -replace " ","" 660 | 661 | $fullregid = "RegID." + $wpfregidYY.text + "-" + $wpfregidMM.text + "." + $wpfregiddomain.text 662 | 663 | $global:detectionscript = "if (test-path 'C:\ProgramData\" + $fullregid + "\" + $fullregid + "_" + $appwithoutspaces + "_" + $appversionwithoutspaces + "_" + $global:architecture +".swidtag') {write-host 'found swidtag'}" 664 | $global:intunedection = 665 | @" 666 | `$check = Test-Path -Path 'C:\ProgramData\$fullregid\$fullregid`_$appwithoutspaces`_$appversionwithoutspaces`_$global:architecture.swidtag' 667 | if (`$check) { 668 | write-output "found swidtag" 669 | Exit 0 670 | } else { 671 | exit 1 672 | } 673 | "@ 674 | Log-Item ("Detection mechanism is : ") 675 | log-item ("$global:detectionscript") 676 | } 677 | Function Generate-IntunePackage{ 678 | 679 | if (-not($WPFTB_IntuneFolder.text.Endswith("\"))) 680 | { 681 | $WPFTB_IntuneFolder.text = $WPFTB_IntuneFolder.text + "\" 682 | } 683 | 684 | $IntunetargetPath = ($global:packagepath).Replace($global:dest,$WPFTB_IntuneFolder.text) 685 | 686 | if (!(test-path $IntunetargetPath)) 687 | { 688 | log-item -logline ("Intune Application target folder does not exist yet, creating now.") 689 | New-Item -itemtype Directory -path $IntunetargetPath -Force 690 | 691 | } 692 | 693 | if ( ((Get-ChildItem $IntunetargetPath | Measure-Object).Count) -gt 0) 694 | { 695 | log-item -logline "Intune Application target folder is NOT empty, deleting content" -severity WARNING 696 | write-host "not empty" 697 | Remove-Item -Path "$IntunetargetPath\*" -recurse -Force 698 | } 699 | else { 700 | log-item -logline "Intune Application target folder is empty, continuing... " 701 | write-host "empty" 702 | } 703 | 704 | # Intune commandline 705 | # IntuneWinAppUtil -c -s -o <-q> 706 | log-item -logline ("Starting Intune package generation") 707 | #$IntunetargetPath = $WPFTB_IntuneFolder.Text 708 | $packagepath = $global:packagepath 709 | $arglist = " -c " + "`"" + $packagepath + "`"" + " -s " + "`"" + "Deploy-Application.exe" + "`"" + " -o " +"`"" + $IntunetargetPath + "`"" 710 | log-item -logline ("Argument list : $arglist") 711 | $exitcode = Start-Process -FilePath "$PSScriptRoot\Intune_Win32\IntuneWinAppUtil.exe" -ArgumentList $arglist -Wait -PassThru 712 | log-item -logline ("exitcodee : $($exitcode.exitcode)") 713 | 714 | if ($exitcode.exitcode -eq 0) 715 | { 716 | $IntuneFile = Get-ChildItem -Path $IntunetargetPath -Filter ("*.intunewin") 717 | log-item -logline ("Generated Intune filename = $IntuneFile") 718 | $Newname = $wpfAppname.text + "_" + $wpfappversion.Text + ".intunewin" 719 | log-item -logline ("New Intune Filename = $Newname") 720 | Rename-Item -Path $IntunetargetPath\$IntuneFile -NewName $Newname -Force 721 | 722 | Generate-Detection 723 | 724 | 'Intune Install Commandline = Deploy-Application.exe -DeploymentType "Install"' | out-file -FilePath "$IntunetargetPath\Instructions.TXT" 725 | 'Intune UnInstall Commandline = Deploy-Application.exe -DeploymentType "UnInstall"' | out-file -FilePath "$IntunetargetPath\Instructions.TXT" -Append 726 | 'Intune Detection Method (Powershell - available in separate Detection_Method.PS1 file) = ' | out-file -FilePath "$IntunetargetPath\Instructions.TXT" -Append 727 | $global:intunedection | out-file -FilePath "$IntunetargetPath\Instructions.TXT" -Append 728 | $global:intunedection | out-file -FilePath "$IntunetargetPath\Detection_Method.PS1" -Encoding ascii 729 | 730 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 731 | [Microsoft.VisualBasic.Interaction]::MsgBox("Intune package created with exitcode $($exitcode.exitcode). Instructions file Generated",'OKOnly,Information',"Intune Package") 732 | 733 | } 734 | else { 735 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 736 | [Microsoft.VisualBasic.Interaction]::MsgBox("Intune package creation failed with exitcode $($exitcode.exitcode).",'critical',"Intune Package") 737 | 738 | } 739 | 740 | 741 | } 742 | 743 | Function Import-SCCM{ 744 | $Appcreated = $false 745 | $appwithoutspaces = "" 746 | $appversionwithoutspaces = "" 747 | $fullregid = "" 748 | #$wpfImport_progress.Content = "Connected to SCCM Site $Sitecode, starting creation of application... Please wait" 749 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Connected to SCCM Site $Sitecode, starting creation of application... Please wait"} ),$null, $null ) 750 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 10 } ),$null, $null ) 751 | 752 | Generate-Detection 753 | 754 | if ($wpfappLanguage.text -ne "") 755 | { 756 | $global:fullname = $wpfappname.text + "_" + $wpfappVersion.text + "_" + $wpfappLanguage.text + "_" + $global:architecture 757 | } 758 | else { 759 | $global:fullname = $wpfappname.text + "_" + $wpfappVersion.text + "_" + $global:architecture 760 | } 761 | 762 | try 763 | { 764 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfCollname.text = $global:fullname} ),$null, $null ) 765 | New-CMApplication -Name $global:fullname -Manufacturer $wpfvendor.text -SoftwareVersion $wpfappversion.text -ErrorAction Stop 766 | #$wpfImport_progress.Content = "Application Created, adding SCCM Deployment Type... Please wait" 767 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 50 } ),$null, $null ) 768 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Application Created, adding SCCM Deployment Type... Please wait"} ),$null, $null ) 769 | log-item ("SCCM Application with name $fullname created") 770 | $appcreated = $true 771 | } 772 | catch 773 | { 774 |     Log-Item -logline "Caught an exception:" -severity "Error" 775 |     log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 776 |     log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 777 | $wpfImport_progress.Foreground="red" 778 | $wpfImport_progress.Content = "Error creating application : $($_.Exception.Message)" 779 | $WPFProgress_SCCM.Foreground="Red" 780 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 100; } ),$null, $null ) 781 | } 782 | 783 | if ($Appcreated) 784 | { 785 | try 786 | { 787 | Add-CMScriptDeploymentType -ApplicationName $global:fullname -ContentLocation $packagepath -DeploymentTypeName $global:fullname -InstallCommand "Deploy-Application.exe -DeploymentType `"Install`"" -UninstallCommand "Deploy-Application.exe -DeploymentType `"UnInstall`"" -Scriptlanguage powerShell -ScriptContent $global:detectionscript -InstallationBehaviorType InstallForSystemIfResourceIsDeviceOtherwiseInstallForUser -ErrorAction Stop 788 | log-item ("Deployment type with name $fullname created") 789 | #$wpfImport_progress.Content = "Done creating SCCM Application" 790 | 791 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfImport_progress.Content = "Done creating SCCM Application"} ),$null, $null ) 792 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFProgress_SCCM.value = 100 } ),$null, $null ) 793 | $wpfGenerate_Collections.Isenabled = $true 794 | $wpfdistribute.isenabled = $true 795 | } 796 | catch 797 | { 798 |     Log-Item -logline "Caught an exception:" -severity "Error" 799 |     log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 800 |     log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 801 | $wpfImport_progress.Content = "Error creating Deployment type: $($_.Exception.Message)" 802 | $WPFProgress_SCCM.Foreground="Red" 803 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $WPFProgress_SCCM.value = 100; } ),$null, $null ) 804 | } 805 | 806 | Try 807 | { 808 | if ($wpfCB_DP.IsChecked) 809 | { 810 | Log-Item -logline "restoring selected dp's" 811 | $storedDPs = $Preferences.Preferences.SelectedDP.DP 812 | foreach ($dp in $storedDPs) 813 | { 814 | if ($dp.name -ne "") 815 | { 816 | $name = $dp.name 817 | Log-Item -logline "selecting $name" 818 | $wpfdp.SelectedItems.Add($dp.name) 819 | } 820 | } 821 | } 822 | } 823 | catch 824 | { 825 |     Log-Item -logline "Failed restoring DP selection" -severity "Error" 826 | Log-Item -logline "Caught an exception:" -severity "Error" 827 |     log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 828 |     log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 829 | 830 | } 831 | 832 | Try 833 | { 834 | if ($wpfCB_DPG.IsChecked) 835 | { 836 | Log-Item -logline "restoring selected dp groups" 837 | $storedDPGs = $Preferences.Preferences.SelectedDPGroup.DPG 838 | 839 | foreach ($dpg in $storedDPGs) 840 | { 841 | if ($dpg.name -ne "") 842 | { 843 | $name = $dpg.name 844 | Log-Item -logline "selecting $name" 845 | $wpfdpgroup.selecteditems.Add($dpg.name) 846 | } 847 | } 848 | } 849 | } 850 | catch 851 | { 852 |     Log-Item -logline "Failed restoring DP Group selection" -severity "Error" 853 | Log-Item -logline "Caught an exception:" -severity "Error" 854 |     log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 855 |     log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 856 | 857 | } 858 | 859 | if ($wpfCB_Auto_Distribute.IsChecked) 860 | { 861 | Distribute-Content 862 | } 863 | 864 | 865 | } 866 | 867 | } 868 | 869 | Function Distribute-Content{ 870 | $TotalselectedDP = $wpfdp.selecteditems.count + $wpfdpgroup.selecteditems.count 871 | Log-item "Total number of selected Dp's and DP groups is : $TotalSelectedDP" 872 | 873 | If ($TotalselectedDP -gt "0") 874 | { 875 | 876 | if ($wpfCB_Auto_Distribute.isenabled -eq $false) {$wpfCB_Auto_Distribute.isenabled = $true} 877 | 878 | [float]$DPBlocks = (100 / $TotalselectedDP) 879 | $dpprogress = 0 880 | 881 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "Preparing $totalselecteddp distribution(s)"} ),$null, $null ) 882 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPB_Progress_distribute.Value = 0} ),$null, $null ) 883 | foreach ($DP in $wpfdp.SelectedItems) 884 | { 885 | Try 886 | { 887 | Start-CMContentDistribution -ApplicationName $fullname -DistributionPointName $dp -erroraction stop 888 | log-item -logline "Starting distribution to DP : $dp" 889 | $dpprogress = $dpprogress + $dpblocks 890 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "Starting distribution to DP : $dp"} ),$null, $null ) 891 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPB_Progress_distribute.Value = $dpprogress} ),$null, $null ) 892 | 893 | 894 | if ($wpfCB_DP.IsChecked) 895 | { 896 | $storedDP = $Preferences.Preferences.SelectedDP.DP | Where-object {$_.Name -eq $dp} | Select-Object -Property Name 897 | if ($storeddp.Name -ne $DP) 898 | { 899 | $item = Select-XML -Xml $Preferences -XPath '//DP' 900 | $newnode = $item.Node.CloneNode($true) 901 | $newnode.Name = $dp 902 | $SelectedDP = Select-XML -Xml $Preferences -XPath '//SelectedDP' 903 | $SelectedDP.Node.AppendChild($newnode) 904 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 905 | log-item "Added $DP to favorite DP's in Preferences XML" 906 | } 907 | else { 908 | log-item "$DP was already a favorite DP in Preferences XML" 909 | } 910 | } 911 | 912 | } 913 | Catch 914 | { 915 | log-item -logline "Distribution to $dp failed" -severity error 916 | log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 917 | log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 918 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "Distribution to $dp failed"} ),$null, $null ) 919 | } 920 | } 921 | foreach ($DPGroup in $wpfdpgroup.selecteditems) 922 | { 923 | try { 924 | Start-CMContentDistribution -ApplicationName $fullname -DistributionPointGroupName $dpgroup -erroraction stop 925 | log-item -logline "Starting distribution to DP group : $DPGroup" 926 | $dpprogress = $dpprogress + $dpblocks 927 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "Starting distribution to DP group : $DPGroup"} ),$null, $null ) 928 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPB_Progress_distribute.Value = $dpprogress} ),$null, $null ) 929 | 930 | if ($wpfCB_DPG.IsChecked) 931 | { 932 | $storedDPG = $Preferences.Preferences.SelectedDPGroup.DPG | Where-object {$_.Name -eq $DPGroup} | Select-Object -Property Name 933 | if ($storedDPG.Name -ne $DPGroup) 934 | { 935 | $item = Select-XML -Xml $Preferences -XPath '//DPG' 936 | $newnode = $item.Node.CloneNode($true) 937 | $newnode.Name = $DPGroup 938 | $SelectedDP = Select-XML -Xml $Preferences -XPath '//SelectedDPGroup' 939 | $SelectedDP.Node.AppendChild($newnode) 940 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 941 | log-item "Added $DPGroup to favorite DP's in Preferences XML" 942 | } 943 | else { 944 | log-item "$DPGroup was already a favorite DP in Preferences XML" 945 | } 946 | } 947 | } 948 | catch { 949 | log-item -logline "Distribution to $dpGroup failed" -severity error 950 | log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 951 | log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 952 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "Distribution to $dpGroup failed"} ),$null, $null ) 953 | } 954 | 955 | } 956 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFdistribute_progress.Content = "All distributions have started"} ),$null, $null ) 957 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFPB_Progress_distribute.Value = 100} ),$null, $null ) 958 | } 959 | else { 960 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 961 | [Microsoft.VisualBasic.Interaction]::MsgBox("No Distribution points or Distribution point groups are selected.",'OKOnly,Information',"Cannot start distribution") 962 | log-item -logline ("No Distribution points or Distribution point groups are selected") -severity Warn 963 | } 964 | } 965 | 966 | Function Generate-Collections{ 967 | $collname_I = $global:fullname 968 | $collname_u = $global:fullname 969 | if ($wpfprefix.text -ne "") 970 | { 971 | $Collname_i = $wpfprefix.text + "-" + $collname_i 972 | $Collname_u = $wpfprefix.text + "-" + $collname_u 973 | 974 | $item = Select-XML -Xml $Preferences -XPath '//Collection[1]' 975 | $item.Node.Prefix = $wpfprefix.text 976 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 977 | 978 | } 979 | if ($wpfsuffix.text -ne "") 980 | { 981 | $collname_i = $collname_i + "-" + $wpfsuffix.text 982 | $collname_U = $collname_U + "-" + $wpfsuffix.text 983 | 984 | $item = Select-XML -Xml $Preferences -XPath '//Collection[1]' 985 | $item.Node.Suffix = $wpfsuffix.text 986 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 987 | } 988 | $collname_i = $collname_i + "-I" 989 | $collname_u = $collname_u + "-U" 990 | $coll_created = $false 991 | try { 992 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Start Creation of collections"} ),$null, $null ) 993 | new-cmcollection -collectiontype device -limitingcollectionID $wpfTB_DefaultLimiting.Text -name $collname_I -erroraction stop 994 | new-cmcollection -collectiontype device -limitingcollectionID $wpfTB_DefaultLimiting.Text -name $collname_u -erroraction stop 995 | log-item -logline "Install collection : $collname_i created" 996 | log-item -logline "Uninstall collection : $collname_u created" 997 | $WPFCollections_info.Background = "Green" 998 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Collection Creation has finished"} ),$null, $null ) 999 | $coll_created = $true 1000 | } 1001 | catch { 1002 | log-item -logline "failed creating collections" -severity error 1003 | log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 1004 | log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 1005 | $wpfCollections_info.background = "Red" 1006 | $WPFCollections_info.Content = "Error creating collections: $($_.Exception.Message)" 1007 | #$Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Error creating collections: $($_.Exception.Message)"} ),$null, $null ) 1008 | 1009 | } 1010 | 1011 | if (($collname_I.Length -le 64) -and ($collname_u.Length -le 64)) 1012 | { 1013 | log-item -logline "AD group names are less than 65 characters, allowing AD group creation if needed" 1014 | $ADGrouplengthOk = $True 1015 | 1016 | } 1017 | else { 1018 | $ADGrouplengthOk = $false 1019 | } 1020 | 1021 | if ($wpfCB_Auto_ADGroup.Ischecked -and $ADGrouplengthOk) 1022 | { 1023 | $ADGroupscreated = $false 1024 | Try { 1025 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Start Creation of AD Groups"} ),$null, $null ) 1026 | New-ADGroup -Path $wpfTB_LDAP.Text -Name $collname_I -GroupScope DomainLocal -GroupCategory Security -erroraction stop 1027 | New-ADGroup -Path $wpfTB_LDAP.Text -Name $collname_U -GroupScope DomainLocal -GroupCategory Security -erroraction stop 1028 | log-item -logline "AD group $collname_i created." 1029 | log-item -logline "AD Group $collname_u created." 1030 | $WPFCollections_info.Background = "Green" 1031 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Creation of AD groups has finished"} ),$null, $null ) 1032 | $ADGroupscreated = $True 1033 | } 1034 | catch { 1035 | log-item -logline "failed creating AD Groups" -severity error 1036 | log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 1037 | log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 1038 | $wpfCollections_info.background = "Red" 1039 | $WPFCollections_info.Content = "Error creating AD groups: $($_.Exception.Message)" 1040 | #$Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Error creating AD groups: $($_.Exception.Message)"} ),$null, $null ) 1041 | } 1042 | } 1043 | elseif ($ADGrouplengthOk -eq $false) { 1044 | log-item -logline "AD group names are more than 65 characters, Can't create AD groups. $($collname_I.Length) characters counted" -severity Error 1045 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 1046 | [Microsoft.VisualBasic.Interaction]::MsgBox("AD group names are more than 65 characters, Can't create AD groups",'critical',"AD groups") 1047 | } 1048 | 1049 | if ($coll_created -and $ADGroupscreated) 1050 | { 1051 | try { 1052 | Log-item -logline "extracting domain details from OU path provided" 1053 | $domaindetails = $wpfTB_LDAP.Text 1054 | $domaindetails = $domaindetails.split(",") 1055 | $DN = "" 1056 | foreach ($item in $domaindetails) 1057 | { 1058 | if ($item.startswith("DC")) 1059 | { 1060 | $DN = $DN + $item + "," 1061 | } 1062 | } 1063 | $DN = $DN.Trimend(",") 1064 | Log-Item -logline "Extracted Distringuished name is $DN" 1065 | $domain = Get-AdDomain -Identity $DN 1066 | $DomainName = $Domain.NetBiosName 1067 | Log-item -logline ("NetBios name is $DomainName") 1068 | $Query_I = 'Select * from SMS_R_System where SMS_R_System.SystemGroupName = "' + $DomainName +'\\' + $collname_i +'"' 1069 | $Query_U = 'Select * from SMS_R_System where SMS_R_System.SystemGroupName = "' + $DomainName +'\\' + $collname_U +'"' 1070 | log-item -logline "Domain name = $DomainName" 1071 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "Adding AD groups as members of the collection"} ),$null, $null ) 1072 | Add-CMDeviceCollectionQueryMembershipRule -CollectionName $collname_i -QueryExpression $Query_I -RuleName "AD group $collname_i" -ErrorAction Stop 1073 | Add-CMDeviceCollectionQueryMembershipRule -CollectionName $collname_u -QueryExpression $Query_U -RuleName "AD group $collname_u" -ErrorAction Stop 1074 | log-item -logline "AD group $collname_i added to collection $collname_i" 1075 | log-item -logline "AD group $collname_u added to collection $collname_u" 1076 | $WPFCollections_info.Background = "Green" 1077 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "AD groups are now members of the collections"} ),$null, $null ) 1078 | } 1079 | Catch { 1080 | log-item -logline "failed creating Collection membership rules for AD groups" -severity error 1081 | log-item -logline "Exception Type: $($_.Exception.GetType().FullName)" -severity "Error" 1082 | log-item -logline "Exception Message: $($_.Exception.Message)" -severity "Error" 1083 | $wpfCollections_info.background = "Red" 1084 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$WPFCollections_info.Content = "failed adding AD Groups as collection members"} ),$null, $null ) 1085 | 1086 | 1087 | } 1088 | } 1089 | } 1090 | 1091 | $inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^" 1262 | { 1263 | $writeoutput = ' [string]$appVendor = ''' + $wpfvendor.text + "'" 1264 | } 1265 | "" 1266 | { 1267 | $writeoutput = ' [string]$appVendor_WithSpace = ''' + $wpfvendor.text + "'" 1268 | } 1269 | "" 1270 | { 1271 | $writeoutput = ' [string]$appName = ''' + $wpfAppname.text + "'" 1272 | } 1273 | "" 1274 | { 1275 | $writeoutput = ' [string]$appName_WithSpace = ''' + $wpfAppname.text + "'" 1276 | } 1277 | "" 1278 | { 1279 | $writeoutput = ' [string]$appVersion = ''' + $wpfAppversion.text + "'" 1280 | } 1281 | "" 1282 | {Switch ($wpfApparchitecture.SelectedIndex) 1283 | { 1284 | "0" 1285 | {$writeoutput = ' [string]$appArch = ''X86''' 1286 | } 1287 | "1" 1288 | {$writeoutput = ' [string]$appArch = ''X64''' 1289 | } 1290 | } 1291 | } 1292 | "" 1293 | {$writeoutput = ' [string]$appLang = ''' + $wpfapplanguage.text + "'" 1294 | } 1295 | "" 1296 | {$writeoutput = ' [string]$appRevision = ''' + $wpfapprevision.text + "'" 1297 | } 1298 | "" 1299 | {$writeoutput = ' [string]$appVendorSWIDTAGRegID = ''' + $FullRegid + "'" 1300 | } 1301 | "" 1302 | {$Writeoutput = ' [string]$appScriptVersion = ''' + '1.0.0' + "'" 1303 | } 1304 | "" 1305 | {$username = [Security.Principal.WindowsIdentity]::GetCurrent().Name 1306 | $Writeoutput = ' [string]$appScriptAuthor = ''' + $username + "'" 1307 | } 1308 | "" 1309 | {$date = get-date -format dd/MM/yyyy 1310 | $Writeoutput = ' [string]$appScriptDate = ''' + $date + "'" 1311 | } 1312 | "" 1313 | { Switch ($WPFInstallType.SelectedIndex) 1314 | { 1315 | "0" 1316 | { 1317 | if ($($WPFInstallPath_Parameters.Text) -ne "") 1318 | { 1319 | log-item -logline ("MSI parameter included,adding it to commandline") 1320 | $writeoutput = ' Execute-MSI -Action ''Install'' -path ''' + $wpfinstallpath.Text + "' -Parameters '" + $WPFInstallPath_Parameters.Text + "'" 1321 | } 1322 | else 1323 | { 1324 | log-item -logline ("NO MSI parameter included, skipping part of commandline") 1325 | $writeoutput = ' Execute-MSI -Action ''Install'' -path ''' + $wpfinstallpath.Text + "'" 1326 | } 1327 | } 1328 | "1" 1329 | { 1330 | if ($($WPFInstallPath_Parameters.Text) -ne "") 1331 | { 1332 | log-item -logline ("Script parameters included,adding it to commandline") 1333 | $writeoutput = ' Execute-Process -path ''' + $wpfinstallpath.Text + "' -Parameters '" + $WPFInstallPath_Parameters.Text + "'" 1334 | } 1335 | else 1336 | { 1337 | log-item -logline ("Script parameters Not included,skipping part of commandline") 1338 | $writeoutput = ' Execute-Process -path ''' + $wpfinstallpath.Text + "'" 1339 | } 1340 | } 1341 | } 1342 | } 1343 | "" 1344 | { Switch ($WPFUnInstallType.SelectedIndex) 1345 | { 1346 | "0" 1347 | { 1348 | $writeoutput = ' Remove-MSIApplications -Name ''' + $WPFUninstallCmd.Text + "'" 1349 | } 1350 | "1" 1351 | { 1352 | $writeoutput = ' Execute-Process -path ''' + $WPFUninstallCmd.Text + "'" 1353 | } 1354 | "2" 1355 | { 1356 | $writeoutput = ' Execute-MSI -Action ''' + 'Uninstall' + "'" + ' -Path ''' + $WPFUninstallCmd.Text + "'" 1357 | } 1358 | } 1359 | } 1360 | "" 1361 | { 1362 | Switch ($WPFLicense.SelectedIndex) 1363 | { 1364 | "1" { 1365 | $writeoutput = 1366 | @" 1367 | ## Writes unique file in ProgramData folder 1368 | Write-Log -Message "Create and Write SWIDTag in ProgramData Folder" -Source 'Install' -LogType 'CMTrace' 1369 | Write-SWIDTag -CreatorRegid `$appVendorSWIDTAGRegID -EntitlementRequired `$FALSE 1370 | if ((get-CimClass -ClassName "sms_client" -Namespace root\ccm -ErrorAction SilentlyContinue).count -gt 0) 1371 |          {Invoke-SCCMTask 'HardwareInventory'} 1372 |          else 1373 |          { 1374 |              Write-Log -Message "No SCCM Client WMI namespace found, not triggering hardware inventory" -Source 'Install' -LogType 'CMTrace' 1375 |          } 1376 | "@ 1377 | 1378 | } 1379 | "0" 1380 | { 1381 | $writeoutput = 1382 | @" 1383 | ## Writes unique file in ProgramData folder 1384 | Write-Log -Message "Create and Write SWIDTag in ProgramData Folder" -Source 'Install' -LogType 'CMTrace' 1385 | Write-SWIDTag -CreatorRegid `$appVendorSWIDTAGRegID -EntitlementRequired `$TRUE 1386 | if ((get-CimClass -ClassName "sms_client" -Namespace root\ccm -ErrorAction SilentlyContinue).count -gt 0) 1387 |          {Invoke-SCCMTask 'HardwareInventory'} 1388 |          else 1389 |          { 1390 |              Write-Log -Message "No SCCM Client WMI namespace found, not triggering hardware inventory" -Source 'Install' -LogType 'CMTrace' 1391 |          } 1392 | "@ 1393 | } 1394 | 1395 | } 1396 | } 1397 | "" 1398 | { 1399 | $writeoutput = 1400 | @" 1401 | ## Removes unique file from ProgramData folder 1402 | `$appversionwithoutspaces = `$AppVersion -replace " ","" 1403 | `$Appwithoutspaces = `$appName_WithSpace -replace " ", "_" 1404 | Write-Log -Message "Remove SWIDTAG File" -Source 'UnInstall' -LogType 'CMTrace' 1405 | `$tag = `$env:ProgramData + '\' + `$appVendorSWIDTAGRegID + '\' + `$appVendorSWIDTAGRegID + '_' + `$Appwithoutspaces + '_' + `$appversionwithoutspaces + '_' + `$AppArch +'.swidtag' 1406 | Remove-File -Path `$Tag 1407 | Invoke-SCCMTask 'HardwareInventory' 1408 | "@ 1409 | 1410 | } 1411 | "" 1412 | { 1413 | If ($wpfTB_EndMessage.Text -eq "") 1414 | { 1415 | $Writeoutput = " # Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install or remove it completely for unattended installations.' -ButtonRightText 'OK' -Icon Information -NoWait" 1416 | } 1417 | Else 1418 | { 1419 | $Writeoutput = " Show-InstallationPrompt -Message '" + $wpfTB_EndMessage.Text + "' -ButtonRightText 'OK' -Icon Information -NoWait" 1420 | } 1421 | } 1422 | default 1423 | { 1424 | $writeoutput = $inputApptoolkit[$i] 1425 | $defaultline = $true 1426 | } 1427 | } 1428 | 1429 | $writeoutput | out-file $output -append 1430 | if ($defaultline -eq $false) 1431 | {log-Item("Adding $writeoutput on line $i")} 1432 | $progress += $blocks 1433 | 1434 | #$wpfprogress.Dispatcher.Invoke("Render", ([Action]{$wpfprogress.Value = $progress}),"Normal",$null ) 1435 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $wpfprogress.Value = $progress; } ),$null, $null ) 1436 | 1437 | 1438 | 1439 | } 1440 | $wpfprogress.Value = 100 1441 | $WPFSCCM.isenabled = $true 1442 | 1443 | if ($wpfCB_Intunepackage.IsChecked) 1444 | { 1445 | Generate-IntunePackage 1446 | } 1447 | 1448 | if ($wpfCB_Auto_Sccm.IsChecked) 1449 | { 1450 | $WPFSCCM.IsSelected = $True 1451 | Connect-SCCM 1452 | } 1453 | 1454 | #$wpfsccm.background = "green" 1455 | #$wpfpackage_progress.Content = "Finished creating package on destination location" 1456 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfpackage_progress.Content = "Finished creating package on destination location"} ),$null, $null ) 1457 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{$wpfsccm.background = "green" } ),$null, $null ) 1458 | $Form.Dispatcher.Invoke( "Render", ([Action``2[[System.Object,mscorlib],[System.Object, mscorlib]]]{ $wpfprogress.Value = $progress } ),$null, $null ) 1459 | } 1460 | }) 1461 | 1462 | $WPFConnect_SCCM.add_click( 1463 | { 1464 | Connect-SCCM 1465 | } 1466 | ) 1467 | 1468 | $WPFImport_SCCM.add_Click( 1469 | { 1470 | Import-SCCM 1471 | }) 1472 | 1473 | $wpfdistribute.add_click( 1474 | { 1475 | Distribute-Content 1476 | }) 1477 | 1478 | $wpfgenerate_collections.add_click({ 1479 | 1480 | Generate-Collections 1481 | }) 1482 | 1483 | $wpfinstalltype.add_Selectionchanged( 1484 | { 1485 | Switch ($WPFInstallType.SelectedIndex) 1486 | { 1487 | "0" 1488 | { 1489 | $wpfinstalltext1.text = "MSI Filename" 1490 | $WPFInstallText2.text = "MSI Parameters" 1491 | } 1492 | "1" 1493 | { 1494 | $wpfinstalltext1.text = "Script executable" 1495 | $WPFInstallText2.text = "Script Parameters" 1496 | } 1497 | } 1498 | }) 1499 | 1500 | $wpfUnInstallType.add_Selectionchanged( 1501 | { 1502 | Switch ($WPFUnInstallType.SelectedIndex) 1503 | { 1504 | "0" 1505 | { 1506 | $WPFUninstallTxt.text = "Application name in Add/Remove programs to uninstall" 1507 | $WPFUninstallCmd.tooltip = "if you put in 'Adobe', it removes all versions of software that match the name 'Adobe'" 1508 | } 1509 | "1" 1510 | { 1511 | $WPFUninstallTxt.text = "Full Uninstall Command-Line" 1512 | $WPFUninstallCmd.tooltip = "Please provide the full uninstall Command-Line" 1513 | } 1514 | "2" 1515 | { 1516 | $WPFUninstallTxt.text = "MSI Product Code" 1517 | $WPFUninstallCmd.tooltip = "Please provide the MSI Product Code" 1518 | } 1519 | } 1520 | 1521 | } 1522 | ) 1523 | 1524 | $wpfinputSource.add_gotfocus({ 1525 | 1526 | if($wpfinputSource.Tag -ne 'cleared') 1527 | { # clear the text box 1528 | $wpfinputSource.Text = '' 1529 | $wpfinputSource.Tag = 'cleared' # use any text you want to indicate that its cleared 1530 | } 1531 | }) 1532 | 1533 | 1534 | $wpfinputdestination.add_gotfocus({ 1535 | 1536 | if($wpfinputdestination.Tag -ne 'cleared') 1537 | { # clear the text box 1538 | $wpfinputdestination.Text = '' 1539 | $wpfinputdestination.Tag = 'cleared' # use any text you want to indicate that its cleared 1540 | } 1541 | }) 1542 | 1543 | $wpfregidYY.add_gotfocus({ 1544 | if($wpfregidYY.Tag -ne 'cleared') 1545 | { # clear the text box 1546 | $wpfregidYY.Text = '' 1547 | $wpfregidYY.Tag = 'cleared' # use any text you want to indicate that its cleared 1548 | } 1549 | }) 1550 | 1551 | $wpfregidMM.add_gotfocus({ 1552 | if($wpfregidMM.Tag -ne 'cleared') 1553 | { # clear the text box 1554 | $wpfregidMM.Text = '' 1555 | $wpfregidMM.Tag = 'cleared' # use any text you want to indicate that its cleared 1556 | } 1557 | }) 1558 | 1559 | $wpfregidDomain.add_gotfocus({ 1560 | if($wpfregidDomain.Tag -ne 'cleared') 1561 | { # clear the text box 1562 | $wpfregidDomain.Text = '' 1563 | $wpfregidDomain.Tag = 'cleared' # use any text you want to indicate that its cleared 1564 | } 1565 | }) 1566 | 1567 | $wpfCB_Intunepackage.Add_Click({ 1568 | if ($wpfCB_Intunepackage.IsChecked) 1569 | { 1570 | $WPFTB_IntuneFolder.IsEnabled = $true 1571 | } 1572 | else { 1573 | $WPFTB_IntuneFolder.IsEnabled = $false 1574 | } 1575 | }) 1576 | 1577 | $wpfBTN_SaveSettings.add_click({ 1578 | 1579 | $SaveOKIntune = $False 1580 | $SaveOKAD = $False 1581 | 1582 | if ($wpfCB_SourcePath.IsChecked){ 1583 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1584 | $item.Node.Save_SP = "True" 1585 | } 1586 | else { 1587 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1588 | $item.Node.Save_SP = "False" 1589 | } 1590 | if ($wpfCB_DestinationPath.IsChecked){ 1591 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1592 | $item.Node.Save_DP = "True" 1593 | } 1594 | else { 1595 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1596 | $item.Node.Save_DP = "False" 1597 | } 1598 | if ($wpfCB_SiteCode.IsChecked){ 1599 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1600 | $item.Node.Save_SiteCode = "True" 1601 | } 1602 | else { 1603 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1604 | $item.Node.Save_SiteCode = "False" 1605 | } 1606 | if ($wpfCB_DP.IsChecked){ 1607 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1608 | $item.Node.Save_DistPoint = "True" 1609 | } 1610 | else { 1611 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1612 | $item.Node.Save_DistPoint = "False" 1613 | } 1614 | if ($wpfCB_DPG.IsChecked){ 1615 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1616 | $item.Node.Save_DistGroup = "True" 1617 | } 1618 | else { 1619 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1620 | $item.Node.Save_DistGroup = "False" 1621 | } 1622 | 1623 | if ($wpfCB_Auto_Sccm.IsChecked){ 1624 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1625 | $item.Node.Autoconnect = "True" 1626 | } 1627 | else { 1628 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1629 | $item.Node.Autoconnect = "False" 1630 | } 1631 | if ($wpfCB_Auto_Import.IsChecked){ 1632 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1633 | $item.Node.AutoImport = "True" 1634 | } 1635 | else { 1636 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1637 | $item.Node.AutoImport = "False" 1638 | } 1639 | if ($wpfCB_Auto_Distribute.IsChecked){ 1640 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1641 | $item.Node.AutoDistribute = "True" 1642 | } 1643 | else { 1644 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1645 | $item.Node.AutoDistribute = "False" 1646 | } 1647 | if ($wpfCB_Auto_Collection.IsChecked){ 1648 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1649 | $item.Node.AutoCollection = "True" 1650 | } 1651 | else { 1652 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1653 | $item.Node.AutoCollection = "False" 1654 | } 1655 | if ($wpfCB_Auto_ADGroup.IsChecked){ 1656 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1657 | $item.Node.AutoADGroup = "True" 1658 | # $wpfCB_CreateAD.IsChecked = $True 1659 | } 1660 | else { 1661 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1662 | $item.Node.AutoADGroup = "False" 1663 | # $wpfCB_CreateAD.IsChecked = $false 1664 | } 1665 | 1666 | #add node for IntunePackage to XML if it does not exist yet 1667 | $item = Select-XML -Xml $Preferences -XPath '//Settings' 1668 | if ($item.Node.IntunePackage -eq $null) 1669 | { 1670 | log-item -logline ("Adding node for IntunePackage") 1671 | $xmlElt = $Preferences.CreateElement("IntunePackage") 1672 | $Preferences.GetElementsByTagName("Settings")[0].AppendChild($xmlElt) 1673 | 1674 | } 1675 | else { 1676 | log-item -logline ("Node for IntunePackage already existed.") 1677 | } 1678 | 1679 | #add node for Intune Appfolder to XML if it does not exist yet 1680 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs' 1681 | if ($item.Node.IntuneAppfolder -eq $null) 1682 | { 1683 | log-item -logline ("Adding node for Intune Appfolder") 1684 | $xmlElt = $Preferences.CreateElement("IntuneAppfolder") 1685 | $Preferences.GetElementsByTagName("SCCM_Prefs")[0].AppendChild($xmlElt) 1686 | 1687 | } 1688 | else { 1689 | log-item -logline ("Node for Intune Appfolder already existed.") 1690 | } 1691 | 1692 | 1693 | $intunepathok = $false 1694 | if ($wpfCB_Intunepackage.IsChecked) 1695 | { 1696 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1697 | $item.Node.IntunePackage = "True" 1698 | if (Test-Path $WPFTB_IntuneFolder.Text) 1699 | { 1700 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1701 | $item.Node.IntuneAppfolder = $WPFTB_IntuneFolder.Text 1702 | log-item "Intune target path exists, continuing" 1703 | $intunepathok = $true 1704 | $SaveOKIntune = $True 1705 | $WPFTB_IntuneFolder.Background = "green" 1706 | } 1707 | else { 1708 | log-item "Intune target path does not exist, unable to save preferences" -severity error 1709 | $intunepathok = $false 1710 | $SaveOKIntune = $False 1711 | $WPFTB_IntuneFolder.Background = "red" 1712 | } 1713 | 1714 | 1715 | } 1716 | else { 1717 | $item = Select-XML -Xml $Preferences -XPath '//Settings[1]' 1718 | $item.Node.IntunePackage = "False" 1719 | $SaveOKIntune = $True 1720 | } 1721 | 1722 | $item = Select-XML -Xml $Preferences -XPath '//SCCM_Prefs[1]' 1723 | $item.Node.LimitingCollection = $wpfTB_DefaultLimiting.Text 1724 | 1725 | 1726 | if ($wpfCB_Auto_ADGroup.IsChecked) 1727 | { 1728 | $exists = $false 1729 | log-item -logline "Checking if OU : $($wpfTB_LDAP.Text) exists." 1730 | $exists = $(try { Get-ADOrganizationalUnit -Identity $wpfTB_LDAP.Text -ErrorAction Ignore } catch{}) -ne $null 1731 | if ($exists) 1732 | { 1733 | log-item -logline "OU Exists, continuing" 1734 | $item.Node.LDAP = $wpfTB_LDAP.Text 1735 | $wpfTB_LDAP.Background = "green" 1736 | $SaveOKAD = $True 1737 | } 1738 | else { 1739 | log-item -logline "OU does not Exist, not saving details" -severity error 1740 | $wpfTB_LDAP.Background = "red" 1741 | } 1742 | 1743 | } 1744 | else { 1745 | $SaveOKAD = $True 1746 | } 1747 | 1748 | #$item.Node.IntunePath = $wpfTB_IntuneFolder.Text 1749 | if (($SaveOKIntune) -and ($SaveOKAD)) 1750 | { 1751 | $Preferences.Save("$psscriptroot\config\Prefs.xml") 1752 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 1753 | [Microsoft.VisualBasic.Interaction]::MsgBox("Settings Saved",'OKOnly,Information',"Settings Update") 1754 | log-item "Settings saved" 1755 | } 1756 | else { 1757 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null 1758 | [Microsoft.VisualBasic.Interaction]::MsgBox("Unable to save settings",'critical',"Settings Update") 1759 | log-item "Unable to save settings" 1760 | } 1761 | 1762 | }) 1763 | 1764 | $WPFBTN_FilePicker.add_click({ 1765 | File-Picker 1766 | }) 1767 | 1768 | $wpfVendor.add_Lostfocus({ 1769 | Fill-swid 1770 | }) 1771 | 1772 | $form.add_closing({ 1773 | Set-Location $global:Startlocation 1774 | log-item -logline "Exiting application" 1775 | }) 1776 | $form.title = "Powershell App deployment Tookit - GUI - Version $Gui_version" 1777 | $Form.ShowDialog() | out-null 1778 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSADT_GUI 2 | Powershell based GUI for the Powershell App Deployment Toolkit 3 | 4 | Detailed information can be found here (for now): 5 | https://www.oscc.be/sccm/configmgr/powershell/posh/intune/Powershell-App-deployment-toolkit-GUI/ 6 | -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Toolkit/AppDeployToolkit/AppDeployToolkitBanner.png -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitConfig.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Toolkit/AppDeployToolkit/AppDeployToolkitConfig.xml -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitExtensions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script is a template that allows you to extend the toolkit with your own custom functions. 4 | .DESCRIPTION 5 | The script is automatically dot-sourced by the AppDeployToolkitMain.ps1 script. 6 | .NOTES 7 | Toolkit Exit Code Ranges: 8 | 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 9 | 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 10 | 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 11 | .LINK 12 | http://psappdeploytoolkit.com 13 | #> 14 | [CmdletBinding()] 15 | Param ( 16 | ) 17 | 18 | ##*=============================================== 19 | ##* VARIABLE DECLARATION 20 | ##*=============================================== 21 | 22 | # Variables: Script 23 | [string]$appDeployToolkitExtName = 'PSAppDeployToolkitExt' 24 | [string]$appDeployExtScriptFriendlyName = 'App Deploy Toolkit Extensions' 25 | [version]$appDeployExtScriptVersion = [version]'1.5.0' 26 | [string]$appDeployExtScriptDate = '06/11/2015' 27 | [hashtable]$appDeployExtScriptParameters = $PSBoundParameters 28 | 29 | ##*=============================================== 30 | ##* FUNCTION LISTINGS 31 | ##*=============================================== 32 | 33 | 34 | #region Function Write-SWIDTag 35 | Function Write-SWIDTag { 36 | <# 37 | .SYNOPSIS 38 | Writes an SWIDTag xmlfile as specified in ISO/IEC 19770 around Software assetmanagement .DESCRIPTION 39 | Writes an SWIDTag xmlfile as specified in ISO/IEC 19770 around Software assetmanagement 40 | Which can help alot in software asset management and license compliance .PARAMETER SWIDTagFilename 41 | eg: regid.1995-08.com.techsmith Snagit 12.swidtag saved in $env:ProgramData 42 | .PARAMETER EntitlementRequired 43 | Does the software require a license to be used. 44 | Optional - Default is $true 45 | .PARAMETER ProductTitle 46 | Title of the software installed. 47 | Optional - Default is $appname when used in powershell app deployment toolkit 48 | .PARAMETER ProductVersion 49 | Version of the software in x.y.z.w format, where x is major, y is minor, z is build and w is review. 50 | Optional - Default is $appversion when used in powershell app deployment toolkit 51 | If your product version has fewer levels, complete with 0 for any level (minor, build or review) you don't have. 52 | .PARAMETER CreatorName 53 | Name of the software creator. 54 | Optional - Default is $appvendor when used in powershell app deployme 55 | Path of the SWIDTag file to create. 56 | Optional - Default $env:ProgramData + '\' + $CreatorRegid + '\' + $creatorRegid + ' ' + $ProductTitle + ' ' + $ProductVersion +'.swidtag' 57 | .PARAMETER CreatorRegid 58 | Regid of the software creator. Regid's in ISO/IEC 19770 are defined as the word regid. followed by the date you owned a particular DNS domain in YYYY-MM format 59 | followed by the domain name in reverse formatting. eg: regid.1991-06.com.microsoft 60 | Mandatory field 61 | .PARAMETER LicensorName 62 | Name of the software licensor. Optional - Default is $appvendor when used in powershell app deployment toolkit 63 | .PARAMETER LicensorRegid 64 | Regid of the software Licensor. Regid's in ISO/IEC 19770 are defined as the word regid. followed by the date you owned a particular DNS domain in YYYY-MM format 65 | followed by the domain name in reverse formatting. eg: regid.1991-06.com.microsoft 66 | Optional - Default is $CreatorRegid value 67 | .PARAMETER SoftwareUniqueID 68 | UniqueID assigned to the software. 69 | Can be set in GUID format 8HexCharacters-4HexCharacters-4HexCharacters-4HexCharacters-12HexCharacters, the format used by MSI productid's 70 | eg: BDFD9ADC-3F97-4A8A-A533-987B21776449 71 | Optional - Default is $producttitle + ' ' + $productversion 72 | .PARAMETER TagCreatorRegid 73 | Regid of the TagCreator. Regid's in ISO/IEC 19770 are defined as the word regid. followed by the date you owned a particular DNS domain in YYYY-MM format 74 | followed by the domain name in reverse formatting. eg: regid.1991-06.com.microsoft 75 | Optional - Default is $CreatorRegid value 76 | 77 | 78 | 79 | .EXAMPLE 80 | Write-SWIDTag -CreatorRegid 'regid.2008-03.be.oscc' 81 | Write a software id tag using the minimum set of parameters to specify, other parameters will use the powershell app deployment toolkit variables for software title, version, vendor to generate the tag. 82 | This example will not work outside the Powershell app deployment toolkit .EXAMPLE 83 | Write-SWIDTag -ProductTitle "SWIDTagHandler" -ProductVersion "1.2.3.4" -CreatorName "OSCC" -CreatorRegid "regid.2008-03.be.oscc" -SoftwareUniqueid "a6ca313e-c7ae-447c-9ee0-bd872278c166" 84 | Write a software id tag using the minimum set of parameters when used outside of the powershell app deployment toolkit 85 | This generates a tag for a product called Swidtaghandler with version 1.2.3.4 by Software vendor OSCC, oscc's regid is regid.2008-30.BE.OSCC .NOTES 86 | You can generate a guid in powershell using this code [guid]::NewGuid(), don't generate a new one for every install. The idea of the guid is that is the same on every machine you write this swidtag to 87 | If this is to install an msi, the ps app deployment toolkit containts a function to get the productcode from that msi, please use that productcode as your guid, the following command returns the productcode from an msi. 88 | Get-MsiTableProperty -Path RelativePathToMsiFile | select productcode .NOTES 89 | the regid as definied in ISO19770 is the literal string regid, followed by the date the domain was first registered in YYYY-MM format, followed by the domain reversed 90 | eg: regid.1991-06.com.microsoft 91 | You can typically find the registration date using whois 92 | 93 | .LINK 94 | http://psappdeploytoolkit.codeplex.com 95 | .LINK 96 | http://www.scug.be/thewmiguy 97 | .LINK 98 | http://www.oscc.be 99 | .LINK 100 | https://technet.microsoft.com/en-us/library/gg681998.aspx#BKMK_WhatsNewSP1 101 | .LINK 102 | http://www.scconfigmgr.com/2014/08/22/how-to-get-msi-file-information-with-powershell/ 103 | #> 104 | [CmdletBinding()] 105 | Param ( 106 | 107 | [Parameter(Mandatory=$false, 108 | HelpMessage='Does the software license require an entitlement also known as software usage right or license?')] 109 | [boolean]$EntitlementRequired = $true, 110 | 111 | [Parameter(Mandatory=$False, 112 | ValueFromPipeline=$True, 113 | ValueFromPipelineByPropertyName=$True, 114 | HelpMessage='Name of the software to add to the swidtag, default is the $appName variable from the PS app deploy toolkit.')] 115 | [Alias('SoftwareName','ApplicationName','Name')] 116 | [ValidateNotNullorEmpty()] 117 | [string] $ProductTitle = $appName_withspace, 118 | 119 | [Parameter(Mandatory=$False, 120 | ValueFromPipeline=$True, 121 | ValueFromPipelineByPropertyName=$True, 122 | HelpMessage='32 or 64 bit architecture, default is the $appArch variable from the PS app deploy toolkit.')] 123 | [Alias('AppArchitecture','ApplicationArchitecture','BitNess')] 124 | [ValidateSet('x86','X86','x64','X64')] 125 | [string] $ProductArch = $appArch, 126 | 127 | [Parameter(Mandatory=$False, 128 | ValueFromPipeline=$True, 129 | ValueFromPipelineByPropertyName=$True, 130 | HelpMessage='Version of the software to add to the swidtag, in 4 digit format separated by the . character')] 131 | [Alias('SoftwareVersion','ApplicationVersion','Version')] 132 | #[ValidatePattern("^\d+\.\d+\.\d+\.\d+$")] 133 | [string] $ProductVersion = $appVersion, 134 | 135 | [Parameter(Mandatory=$False, 136 | ValueFromPipeline=$True, 137 | ValueFromPipelineByPropertyName=$True, 138 | HelpMessage='Path, including filename of the swidtag to generate.')] 139 | [Alias('Filename')] 140 | [string] $SWIDTagFilename = $env:ProgramData + '\' + $CreatorRegid + '\' + $creatorRegid + ' ' + $ProductTitle + ' ' + $ProductVersion + ' ' + $productArch +'.swidtag', 141 | 142 | [Parameter(Mandatory=$False, 143 | ValueFromPipeline=$True, 144 | ValueFromPipelineByPropertyName=$True, 145 | HelpMessage='Vendor of the software to add to the swidtag, default is the $appvendor variable in the PS app deploy toolkit.')] 146 | [Alias('SoftwareVendor','Vendor','Manufacturer')] 147 | [ValidateNotNullorEmpty()] 148 | [string] $CreatorName = $appVendor_withspace, 149 | 150 | [Parameter(Mandatory=$True, 151 | ValueFromPipeline=$True, 152 | ValueFromPipelineByPropertyName=$True, 153 | HelpMessage='Regid of the Vendor of the software to add to the swidtag, format is: regid.YYYY-MM.FirstLevelDomain.SecondLeveldomain, eg: regid.1991-06.com.microsoft the date is the date when the domain referenced was first registered')] 154 | [Alias('Regid','VendorRegid')] 155 | #[ValidatePattern("^regid\.(19|20)\d\d-(0[1-9]|1[012])\.[A-Za-z]{2,6}\.[A-Za-z0-9-]{1,63}.+$")] 156 | [string]$CreatorRegid, 157 | 158 | [Parameter(Mandatory=$False, 159 | ValueFromPipeline=$True, 160 | ValueFromPipelineByPropertyName=$True, 161 | HelpMessage='LicensorName of the software to add to the swidtag.')] 162 | [ValidateNotNullorEmpty()] 163 | [string] $LicensorName = $CreatorName, 164 | 165 | [Parameter(Mandatory=$False, 166 | ValueFromPipeline=$True, 167 | ValueFromPipelineByPropertyName=$True, 168 | HelpMessage='Licensor Regid of the software to add to the swidtag, format is identical to $CreatorRegid')] 169 | #[ValidatePattern("^regid\.(19|20)\d\d-(0[1-9]|1[012])\.[A-Za-z]{2,6}\.[A-Za-z0-9-]{1,63}.+$")] 170 | [string]$LicensorRegid = $creatorRegid, 171 | 172 | [Parameter(Mandatory=$False, 173 | ValueFromPipeline=$True, 174 | ValueFromPipelineByPropertyName=$True, 175 | HelpMessage='Uinqueid to add to the swid tag, format is: 8 hex chars-4 hex chars-4 hex chars-4 hex chars-12 hex chars. If this is an MSI install use the MSI ProductID, guid can but does not have to be enclosed in {}')] 176 | [Alias('UniqueId','GUID')] 177 | #[ValidatePattern("^{?[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}}?$")] 178 | [string]$SoftwareUniqueid=$ProductTitle + ' ' + $ProductVersion, 179 | 180 | [Parameter(Mandatory=$False, 181 | ValueFromPipeline=$True, 182 | ValueFromPipelineByPropertyName=$True, 183 | HelpMessage='Tag creator name of the software to add to the swidtag.')] 184 | [ValidateNotNullorEmpty()] 185 | [string] $TagCreatorName = $CreatorName, 186 | 187 | [Parameter(Mandatory=$False, 188 | ValueFromPipeline=$True, 189 | ValueFromPipelineByPropertyName=$True, 190 | HelpMessage='TagCreator Regid of the software to add to the swidtag, format is identical to $CreatorRegid')] 191 | #[ValidatePattern("^regid\.(19|20)\d\d-(0[1-9]|1[012])\.[A-Za-z]{2,6}\.[A-Za-z0-9-]{1,63}.+$")] 192 | [string]$TagCreatorRegid = $creatorRegid, 193 | 194 | [Parameter(Mandatory=$false)] 195 | [ValidateNotNullOrEmpty()] 196 | [boolean]$ContinueOnError = $true 197 | ) 198 | 199 | Begin { 200 | ## Get the name of this function and write header 201 | [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name 202 | Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header 203 | } 204 | Process { 205 | Try { 206 | # this is where the document will be saved: 207 | $Path = $SWIDTagFilename.Replace(' ','_') 208 | $SWIDTagFolder = split-path $Path 209 | 210 | Write-Log -Message "Testing whether [$SWIDTagFolder] exists, if not path is recursively created." -Source ${CmdletName} 211 | if(-not(test-path($SWIDTagFolder))) 212 | { 213 | Write-Log -Message "Folder [$SWIDTagFolder] does not exist, recursively creating it." -Source ${CmdletName} 214 | New-Item -path $SWIDTagFolder -type directory 215 | Write-Log -Message "Folder [$SWIDTagFolder] is successfully created." -Source ${CmdletName} 216 | } 217 | else 218 | { 219 | Write-Log -Message "Folder [$SWIDTagFolder] already exists, no need to create it." -Source ${CmdletName} 220 | } 221 | 222 | 223 | 224 | # get an XMLTextWriter to create the XML and set the encoding to UTF8 225 | Write-Log -Message "Creating XMLTextWriter object and set encoding to UTF8." -Source ${CmdletName} 226 | $encoding = [System.Text.Encoding]::UTF8 227 | Write-Log -Message "Creating XMLTextWriter file in path $Path." -Source ${CmdletName} 228 | Write-Log -Message "Creating XMLTextWriter file in path $SWIDTagFileName." -Source ${CmdletName} 229 | $XmlWriter = New-Object System.XMl.XmlTextWriter($Path,$encoding) 230 | 231 | # choose a pretty formatting: (set Indentation to 1 Tab) 232 | Write-Log -Message "Setting indentation of the file to 1 Tab." -Source ${CmdletName} 233 | $xmlWriter.Formatting = 'Indented' 234 | $xmlWriter.Indentation = 1 235 | $XmlWriter.IndentChar = "`t" 236 | 237 | 238 | # write the header 239 | Write-Log -Message "Writing SWIDTag header." -Source ${CmdletName} 240 | $xmlWriter.WriteStartDocument("true") 241 | $xmlWriter.WriteProcessingInstruction("xml-stylesheet", "type='text/xsl' href='style.xsl'") 242 | $XmlWriter.WriteStartElement("swid","software_identification_tag","http://standards.iso.org/iso/19770/-2/2008/schema.xsd") 243 | $XmlWriter.WriteAttributeString("xsi:schemalocation","http://standards.iso.org/iso/19770/-2/2008/schema.xsd software_identification_tag.xsd") 244 | $XmlWriter.WriteAttributeString("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance") 245 | $XmlWriter.WriteAttributeString("xmlns:ds","http://www.w3.org/2000/09/xmldsig#") 246 | # write the mandatory elements, and the sccm supported elements of an iso 19770 swidtag 247 | Write-Log -Message "Writing EntitlementRequired field, settng it to [$EntitleMentRequired]." -Source ${CmdletName} 248 | if ($EntitlementRequired) 249 | { 250 | $XmlWriter.WriteElementString("swid:entitlement_required_indicator","true") 251 | } 252 | else 253 | { 254 | $XmlWriter.WriteElementString("swid:entitlement_required_indicator","false") 255 | } 256 | Write-Log -Message "Writing other SWIDTag elements." -Source ${CmdletName} 257 | Write-Log -Message "Writing ProductTitle, setting it to [$ProductTitle]" -Source ${CmdletName} 258 | $XmlWriter.WriteElementString("swid:product_title",$ProductTitle) 259 | $XmlWriter.WriteStartElement("swid:product_version") 260 | $XmlWriter.WriteElementString("swid:name",$ProductVersion) 261 | $XmlWriter.WriteStartElement("swid:numeric") 262 | if (!($productversion.Contains('.'))) 263 | { 264 | Write-Log -Message "ProductVersion [$ProductVersion] is not in the correct format. Altering format to comply with SwidTag format, appending .0. New Productversion is [$ProductVersion.0]" 265 | $productversion = $productversion + '.0' 266 | } 267 | 268 | Write-Log -Message "Writing ProductVersion, setting it to [$ProductVersion]" -Source ${CmdletName} 269 | $splitProductVersion = $ProductVersion.Split('.') 270 | $XmlWriter.WriteElementString("swid:major",$splitProductversion[0]) 271 | $XmlWriter.WriteElementString("swid:minor",$splitProductversion[1]) 272 | $XmlWriter.WriteElementString("swid:build",$splitProductversion[2]) 273 | $XmlWriter.WriteElementString("swid:review",$splitProductversion[3]) 274 | $XmlWriter.WriteEndElement(); 275 | $XmlWriter.WriteEndElement(); 276 | $XmlWriter.WriteStartElement("swid:software_creator") 277 | $XmlWriter.WriteElementString("swid:name",$CreatorName) 278 | $XmlWriter.WriteElementString("swid:regid",$creatorRegid) 279 | $XmlWriter.WriteEndElement(); 280 | $XmlWriter.WriteStartElement("swid:software_licensor") 281 | $XmlWriter.WriteElementString("swid:name",$LicensorName) 282 | $XmlWriter.WriteElementString("swid:regid",$LicensorRegid) 283 | $XmlWriter.WriteEndElement(); 284 | $XmlWriter.WriteStartElement("swid:software_id") 285 | if ($SoftwareUniqueid.ToString().StartsWith('{')) 286 | { 287 | $swid = $SoftwareUniqueid.ToString().Substring(1,36) 288 | Write-Log -Message "Writing SoftwareuniqueId [$swid] to swid tag." -Source ${CmdletName} 289 | $XmlWriter.WriteElementString("swid:unique_id",$SoftwareUniqueid.ToString().Substring(1,36)) 290 | } 291 | else 292 | { 293 | Write-Log -Message "Writing SoftwareuniqueId [$SoftwareUniqueid] to swid tag." -Source ${CmdletName} 294 | $XmlWriter.WriteElementString("swid:unique_id",$SoftwareUniqueid) 295 | } 296 | $XmlWriter.WriteElementString("swid:tag_creator_regid",$TagCreatorRegid) 297 | $XmlWriter.WriteEndElement(); 298 | $XmlWriter.WriteStartElement("swid:tag_creator") 299 | $XmlWriter.WriteElementString("swid:name",$TagCreatorName) 300 | $XmlWriter.WriteElementString("swid:regid",$TagCreatorRegid) 301 | $XmlWriter.WriteEndElement(); 302 | $xmlWriter.Flush() 303 | $xmlWriter.Close() 304 | } 305 | Catch { 306 | Write-Log -Message "Failed to write swidtag to destination [$SWIDTagFilename]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} 307 | If (-not $ContinueOnError) { 308 | Throw "Failed to write swidtag to destination [$SWIDTagFilename]: $($_.Exception.Message)" 309 | } 310 | } 311 | } 312 | End { 313 | Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer 314 | } 315 | } 316 | #endregion 317 | 318 | 319 | 320 | ##*=============================================== 321 | ##* END FUNCTION LISTINGS 322 | ##*=============================================== 323 | 324 | ##*=============================================== 325 | ##* SCRIPT BODY 326 | ##*=============================================== 327 | 328 | If ($scriptParentPath) { 329 | Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] dot-source invoked by [$(((Get-Variable -Name MyInvocation).Value).ScriptName)]" -Source $appDeployToolkitExtName 330 | } 331 | Else { 332 | Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] invoked directly" -Source $appDeployToolkitExtName 333 | } 334 | 335 | ##*=============================================== 336 | ##* END SCRIPT BODY 337 | ##*=============================================== -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitHelp.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Displays a graphical console to browse the help for the App Deployment Toolkit functions 4 | # LICENSE # 5 | PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. 6 | Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. 7 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . 9 | .DESCRIPTION 10 | Displays a graphical console to browse the help for the App Deployment Toolkit functions 11 | .EXAMPLE 12 | AppDeployToolkitHelp.ps1 13 | .NOTES 14 | .LINK 15 | http://psappdeploytoolkit.com 16 | #> 17 | 18 | ##*=============================================== 19 | ##* VARIABLE DECLARATION 20 | ##*=============================================== 21 | 22 | ## Variables: Script 23 | [string]$appDeployToolkitHelpName = 'PSAppDeployToolkitHelp' 24 | [string]$appDeployHelpScriptFriendlyName = 'App Deploy Toolkit Help' 25 | [version]$appDeployHelpScriptVersion = [version]'3.6.5' 26 | [string]$appDeployHelpScriptDate = '02/12/2017' 27 | 28 | ## Variables: Environment 29 | [string]$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent 30 | # Dot source the App Deploy Toolkit Functions 31 | . "$scriptDirectory\AppDeployToolkitMain.ps1" -DisableLogging 32 | 33 | ##*=============================================== 34 | ##* END VARIABLE DECLARATION 35 | ##*=============================================== 36 | 37 | ##*=============================================== 38 | ##* FUNCTION LISTINGS 39 | ##*=============================================== 40 | 41 | Function Show-HelpConsole { 42 | ## Import the Assemblies 43 | Add-Type -AssemblyName 'System.Windows.Forms' -ErrorAction 'Stop' 44 | Add-Type -AssemblyName System.Drawing -ErrorAction 'Stop' 45 | 46 | ## Form Objects 47 | $HelpForm = New-Object -TypeName 'System.Windows.Forms.Form' 48 | $HelpListBox = New-Object -TypeName 'System.Windows.Forms.ListBox' 49 | $HelpTextBox = New-Object -TypeName 'System.Windows.Forms.RichTextBox' 50 | $InitialFormWindowState = New-Object -TypeName 'System.Windows.Forms.FormWindowState' 51 | 52 | ## Form Code 53 | $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' 54 | $System_Drawing_Size.Height = 665 55 | $System_Drawing_Size.Width = 957 56 | $HelpForm.ClientSize = $System_Drawing_Size 57 | $HelpForm.DataBindings.DefaultDataSourceUpdateMode = 0 58 | $HelpForm.Name = 'HelpForm' 59 | $HelpForm.Text = 'PowerShell App Deployment Toolkit Help Console' 60 | $HelpForm.WindowState = 'Normal' 61 | $HelpForm.ShowInTaskbar = $true 62 | $HelpForm.FormBorderStyle = 'Fixed3D' 63 | $HelpForm.MaximizeBox = $false 64 | $HelpForm.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon 65 | $HelpListBox.Anchor = 7 66 | $HelpListBox.BorderStyle = 1 67 | $HelpListBox.DataBindings.DefaultDataSourceUpdateMode = 0 68 | $HelpListBox.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ('Microsoft Sans Serif', 9.75, 1, 3, 1) 69 | $HelpListBox.FormattingEnabled = $true 70 | $HelpListBox.ItemHeight = 16 71 | $System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' 72 | $System_Drawing_Point.X = 0 73 | $System_Drawing_Point.Y = 0 74 | $HelpListBox.Location = $System_Drawing_Point 75 | $HelpListBox.Name = 'HelpListBox' 76 | $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' 77 | $System_Drawing_Size.Height = 658 78 | $System_Drawing_Size.Width = 271 79 | $HelpListBox.Size = $System_Drawing_Size 80 | $HelpListBox.Sorted = $true 81 | $HelpListBox.TabIndex = 2 82 | $HelpListBox.add_SelectedIndexChanged({ $HelpTextBox.Text = Get-Help -Name $HelpListBox.SelectedItem -Full | Out-String }) 83 | $helpFunctions = Get-Command -CommandType 'Function' | Where-Object { ($_.HelpUri -match 'psappdeploytoolkit') -and ($_.Definition -notmatch 'internal script function') } | Select-Object -ExpandProperty Name 84 | ForEach ($helpFunction in $helpFunctions) { 85 | $null = $HelpListBox.Items.Add($helpFunction) 86 | } 87 | $HelpForm.Controls.Add($HelpListBox) 88 | $HelpTextBox.Anchor = 11 89 | $HelpTextBox.BorderStyle = 1 90 | $HelpTextBox.DataBindings.DefaultDataSourceUpdateMode = 0 91 | $HelpTextBox.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ('Microsoft Sans Serif', 8.5, 0, 3, 1) 92 | $HelpTextBox.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) 93 | $System_Drawing_Point = New-Object -TypeName System.Drawing.Point 94 | $System_Drawing_Point.X = 277 95 | $System_Drawing_Point.Y = 0 96 | $HelpTextBox.Location = $System_Drawing_Point 97 | $HelpTextBox.Name = 'HelpTextBox' 98 | $HelpTextBox.ReadOnly = $True 99 | $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' 100 | $System_Drawing_Size.Height = 658 101 | $System_Drawing_Size.Width = 680 102 | $HelpTextBox.Size = $System_Drawing_Size 103 | $HelpTextBox.TabIndex = 1 104 | $HelpTextBox.Text = '' 105 | $HelpForm.Controls.Add($HelpTextBox) 106 | 107 | ## Save the initial state of the form 108 | $InitialFormWindowState = $HelpForm.WindowState 109 | ## Init the OnLoad event to correct the initial state of the form 110 | $HelpForm.add_Load($OnLoadForm_StateCorrection) 111 | ## Show the Form 112 | $null = $HelpForm.ShowDialog() 113 | } 114 | 115 | ##*=============================================== 116 | ##* END FUNCTION LISTINGS 117 | ##*=============================================== 118 | 119 | ##*=============================================== 120 | ##* SCRIPT BODY 121 | ##*=============================================== 122 | 123 | Write-Log -Message "Load [$appDeployHelpScriptFriendlyName] console..." -Source $appDeployToolkitHelpName 124 | 125 | ## Show the help console 126 | Show-HelpConsole 127 | 128 | Write-Log -Message "[$appDeployHelpScriptFriendlyName] console closed." -Source $appDeployToolkitHelpName 129 | 130 | ##*=============================================== 131 | ##* END SCRIPT BODY 132 | ##*=============================================== -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitLogo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Toolkit/AppDeployToolkit/AppDeployToolkitLogo.ico -------------------------------------------------------------------------------- /Toolkit/AppDeployToolkit/AppDeployToolkitMain.cs: -------------------------------------------------------------------------------- 1 | // Date Modified: 01-08-2016 2 | // Version Number: 3.6.8 3 | 4 | using System; 5 | using System.Text; 6 | using System.Collections; 7 | using System.ComponentModel; 8 | using System.DirectoryServices; 9 | using System.Security.Principal; 10 | using System.Collections.Generic; 11 | using System.Runtime.InteropServices; 12 | using System.Text.RegularExpressions; 13 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; 14 | 15 | namespace PSADT 16 | { 17 | public class Msi 18 | { 19 | enum LoadLibraryFlags : int 20 | { 21 | DONT_RESOLVE_DLL_REFERENCES = 0x00000001, 22 | LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, 23 | LOAD_LIBRARY_AS_DATAFILE = 0x00000002, 24 | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, 25 | LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, 26 | LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 27 | } 28 | 29 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 30 | static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, LoadLibraryFlags dwFlags); 31 | 32 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 33 | static extern int LoadString(IntPtr hInstance, int uID, StringBuilder lpBuffer, int nBufferMax); 34 | 35 | // Get MSI exit code message from msimsg.dll resource dll 36 | public static string GetMessageFromMsiExitCode(int errCode) 37 | { 38 | IntPtr hModuleInstance = LoadLibraryEx("msimsg.dll", IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE); 39 | 40 | StringBuilder sb = new StringBuilder(255); 41 | LoadString(hModuleInstance, errCode, sb, sb.Capacity + 1); 42 | 43 | return sb.ToString(); 44 | } 45 | } 46 | 47 | public class Explorer 48 | { 49 | private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff); 50 | private const int WM_SETTINGCHANGE = 0x1a; 51 | private const int SMTO_ABORTIFHUNG = 0x0002; 52 | 53 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 54 | static extern bool SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); 55 | 56 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 57 | private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, IntPtr lpdwResult); 58 | 59 | [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] 60 | private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); 61 | 62 | public static void RefreshDesktopAndEnvironmentVariables() 63 | { 64 | // Update desktop icons 65 | SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero); 66 | SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero); 67 | // Update environment variables 68 | SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero); 69 | } 70 | } 71 | 72 | public sealed class FileVerb 73 | { 74 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 75 | public static extern int LoadString(IntPtr h, int id, StringBuilder sb, int maxBuffer); 76 | 77 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 78 | public static extern IntPtr LoadLibrary(string s); 79 | 80 | public static string GetPinVerb(int VerbId) 81 | { 82 | IntPtr hShell32 = LoadLibrary("shell32.dll"); 83 | const int nChars = 255; 84 | StringBuilder Buff = new StringBuilder("", nChars); 85 | 86 | LoadString(hShell32, VerbId, Buff, Buff.Capacity); 87 | return Buff.ToString(); 88 | } 89 | } 90 | 91 | public sealed class IniFile 92 | { 93 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 94 | public static extern int GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName); 95 | 96 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 97 | [return: MarshalAs(UnmanagedType.Bool)] 98 | public static extern bool WritePrivateProfileString(string lpAppName, string lpKeyName, StringBuilder lpString, string lpFileName); 99 | 100 | public static string GetIniValue(string section, string key, string filepath) 101 | { 102 | string sDefault = ""; 103 | const int nChars = 1024; 104 | StringBuilder Buff = new StringBuilder(nChars); 105 | 106 | GetPrivateProfileString(section, key, sDefault, Buff, Buff.Capacity, filepath); 107 | return Buff.ToString(); 108 | } 109 | 110 | public static void SetIniValue(string section, string key, StringBuilder value, string filepath) 111 | { 112 | WritePrivateProfileString(section, key, value, filepath); 113 | } 114 | } 115 | 116 | public class UiAutomation 117 | { 118 | public enum GetWindow_Cmd : int 119 | { 120 | GW_HWNDFIRST = 0, 121 | GW_HWNDLAST = 1, 122 | GW_HWNDNEXT = 2, 123 | GW_HWNDPREV = 3, 124 | GW_OWNER = 4, 125 | GW_CHILD = 5, 126 | GW_ENABLEDPOPUP = 6 127 | } 128 | 129 | public enum ShowWindowEnum 130 | { 131 | Hide = 0, 132 | ShowNormal = 1, 133 | ShowMinimized = 2, 134 | ShowMaximized = 3, 135 | Maximize = 3, 136 | ShowNormalNoActivate = 4, 137 | Show = 5, 138 | Minimize = 6, 139 | ShowMinNoActivate = 7, 140 | ShowNoActivate = 8, 141 | Restore = 9, 142 | ShowDefault = 10, 143 | ForceMinimized = 11 144 | } 145 | 146 | public enum UserNotificationState 147 | { 148 | // http://msdn.microsoft.com/en-us/library/bb762533(v=vs.85).aspx 149 | ScreenSaverOrLockedOrFastUserSwitching =1, // A screen saver is displayed, the machine is locked, or a nonactive Fast User Switching session is in progress. 150 | FullScreenOrPresentationModeOrLoginScreen =2, // A full-screen application is running or Presentation Settings are applied. Presentation Settings allow a user to put their machine into a state fit for an uninterrupted presentation, such as a set of PowerPoint slides, with a single click. Also returns this state if machine is at the login screen. 151 | RunningDirect3DFullScreen =3, // A full-screen, exclusive mode, Direct3D application is running. 152 | PresentationMode =4, // The user has activated Windows presentation settings to block notifications and pop-up messages. 153 | AcceptsNotifications =5, // None of the other states are found, notifications can be freely sent. 154 | QuietTime =6, // Introduced in Windows 7. The current user is in "quiet time", which is the first hour after a new user logs into his or her account for the first time. 155 | WindowsStoreAppRunning =7 // Introduced in Windows 8. A Windows Store app is running. 156 | } 157 | 158 | // Only for Vista or above 159 | [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] 160 | static extern int SHQueryUserNotificationState(out UserNotificationState pquns); 161 | 162 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 163 | [return: MarshalAs(UnmanagedType.Bool)] 164 | public static extern bool EnumWindows(EnumWindowsProcD lpEnumFunc, ref IntPtr lParam); 165 | 166 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 167 | public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); 168 | 169 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 170 | public static extern int GetWindowTextLength(IntPtr hWnd); 171 | 172 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 173 | private static extern IntPtr GetDesktopWindow(); 174 | 175 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 176 | private static extern IntPtr GetShellWindow(); 177 | 178 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 179 | [return: MarshalAs(UnmanagedType.Bool)] 180 | public static extern bool IsWindowEnabled(IntPtr hWnd); 181 | 182 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 183 | public static extern bool IsWindowVisible(IntPtr hWnd); 184 | 185 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 186 | [return: MarshalAs(UnmanagedType.Bool)] 187 | public static extern bool IsIconic(IntPtr hWnd); 188 | 189 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 190 | [return: MarshalAs(UnmanagedType.Bool)] 191 | public static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags); 192 | 193 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 194 | public static extern IntPtr SetActiveWindow(IntPtr hwnd); 195 | 196 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 197 | [return: MarshalAs(UnmanagedType.Bool)] 198 | public static extern bool SetForegroundWindow(IntPtr hWnd); 199 | 200 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 201 | public static extern IntPtr GetForegroundWindow(); 202 | 203 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 204 | public static extern IntPtr SetFocus(IntPtr hWnd); 205 | 206 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 207 | public static extern bool BringWindowToTop(IntPtr hWnd); 208 | 209 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 210 | public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); 211 | 212 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 213 | public static extern int GetCurrentThreadId(); 214 | 215 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 216 | public static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach); 217 | 218 | [DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto, SetLastError = false)] 219 | public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex); 220 | 221 | [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Auto, SetLastError = false)] 222 | public static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); 223 | 224 | public delegate bool EnumWindowsProcD(IntPtr hWnd, ref IntPtr lItems); 225 | 226 | public static bool EnumWindowsProc(IntPtr hWnd, ref IntPtr lItems) 227 | { 228 | if (hWnd != IntPtr.Zero) 229 | { 230 | GCHandle hItems = GCHandle.FromIntPtr(lItems); 231 | List items = hItems.Target as List; 232 | items.Add(hWnd); 233 | return true; 234 | } 235 | else 236 | { 237 | return false; 238 | } 239 | } 240 | 241 | public static List EnumWindows() 242 | { 243 | try 244 | { 245 | List items = new List(); 246 | EnumWindowsProcD CallBackPtr = new EnumWindowsProcD(EnumWindowsProc); 247 | GCHandle hItems = GCHandle.Alloc(items); 248 | IntPtr lItems = GCHandle.ToIntPtr(hItems); 249 | EnumWindows(CallBackPtr, ref lItems); 250 | return items; 251 | } 252 | catch (Exception ex) 253 | { 254 | throw new Exception("An error occured during window enumeration: " + ex.Message); 255 | } 256 | } 257 | 258 | public static string GetWindowText(IntPtr hWnd) 259 | { 260 | int iTextLength = GetWindowTextLength(hWnd); 261 | if (iTextLength > 0) 262 | { 263 | StringBuilder sb = new StringBuilder(iTextLength); 264 | GetWindowText(hWnd, sb, iTextLength + 1); 265 | return sb.ToString(); 266 | } 267 | else 268 | { 269 | return String.Empty; 270 | } 271 | } 272 | 273 | public static bool BringWindowToFront(IntPtr windowHandle) 274 | { 275 | bool breturn = false; 276 | if (IsIconic(windowHandle)) 277 | { 278 | // Show minimized window because SetForegroundWindow does not work for minimized windows 279 | ShowWindow(windowHandle, ShowWindowEnum.ShowMaximized); 280 | } 281 | 282 | int lpdwProcessId; 283 | int windowThreadProcessId = GetWindowThreadProcessId(GetForegroundWindow(), out lpdwProcessId); 284 | int currentThreadId = GetCurrentThreadId(); 285 | AttachThreadInput(windowThreadProcessId, currentThreadId, true); 286 | 287 | BringWindowToTop(windowHandle); 288 | breturn = SetForegroundWindow(windowHandle); 289 | SetActiveWindow(windowHandle); 290 | SetFocus(windowHandle); 291 | 292 | AttachThreadInput(windowThreadProcessId, currentThreadId, false); 293 | return breturn; 294 | } 295 | 296 | public static int GetWindowThreadProcessId(IntPtr windowHandle) 297 | { 298 | int processID = 0; 299 | GetWindowThreadProcessId(windowHandle, out processID); 300 | return processID; 301 | } 302 | 303 | public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex) 304 | { 305 | if (IntPtr.Size == 4) 306 | { 307 | return GetWindowLong32(hWnd, nIndex); 308 | } 309 | return GetWindowLongPtr64(hWnd, nIndex); 310 | } 311 | 312 | public static string GetUserNotificationState() 313 | { 314 | // Only works for Windows Vista or higher 315 | UserNotificationState state; 316 | int returnVal = SHQueryUserNotificationState(out state); 317 | return state.ToString(); 318 | } 319 | } 320 | 321 | public class QueryUser 322 | { 323 | [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] 324 | public static extern IntPtr WTSOpenServer(string pServerName); 325 | 326 | [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] 327 | public static extern void WTSCloseServer(IntPtr hServer); 328 | 329 | [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] 330 | public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr pBuffer, out int pBytesReturned); 331 | 332 | [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] 333 | public static extern int WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr pSessionInfo, out int pCount); 334 | 335 | [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] 336 | public static extern void WTSFreeMemory(IntPtr pMemory); 337 | 338 | [DllImport("winsta.dll", CharSet = CharSet.Auto, SetLastError = false)] 339 | public static extern int WinStationQueryInformation(IntPtr hServer, int sessionId, int information, ref WINSTATIONINFORMATIONW pBuffer, int bufferLength, ref int returnedLength); 340 | 341 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 342 | public static extern int GetCurrentProcessId(); 343 | 344 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] 345 | public static extern bool ProcessIdToSessionId(int processId, ref int pSessionId); 346 | 347 | public class TerminalSessionData 348 | { 349 | public int SessionId; 350 | public string ConnectionState; 351 | public string SessionName; 352 | public bool IsUserSession; 353 | public TerminalSessionData(int sessionId, string connState, string sessionName, bool isUserSession) 354 | { 355 | SessionId = sessionId; 356 | ConnectionState = connState; 357 | SessionName = sessionName; 358 | IsUserSession = isUserSession; 359 | } 360 | } 361 | 362 | public class TerminalSessionInfo 363 | { 364 | public string NTAccount; 365 | public string SID; 366 | public string UserName; 367 | public string DomainName; 368 | public int SessionId; 369 | public string SessionName; 370 | public string ConnectState; 371 | public bool IsCurrentSession; 372 | public bool IsConsoleSession; 373 | public bool IsActiveUserSession; 374 | public bool IsUserSession; 375 | public bool IsRdpSession; 376 | public bool IsLocalAdmin; 377 | public DateTime? LogonTime; 378 | public TimeSpan? IdleTime; 379 | public DateTime? DisconnectTime; 380 | public string ClientName; 381 | public string ClientProtocolType; 382 | public string ClientDirectory; 383 | public int ClientBuildNumber; 384 | } 385 | 386 | [StructLayout(LayoutKind.Sequential)] 387 | private struct WTS_SESSION_INFO 388 | { 389 | public Int32 SessionId; 390 | [MarshalAs(UnmanagedType.LPStr)] 391 | public string SessionName; 392 | public WTS_CONNECTSTATE_CLASS State; 393 | } 394 | 395 | [StructLayout(LayoutKind.Sequential)] 396 | public struct WINSTATIONINFORMATIONW 397 | { 398 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 70)] 399 | private byte[] Reserved1; 400 | public int SessionId; 401 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 402 | private byte[] Reserved2; 403 | public FILETIME ConnectTime; 404 | public FILETIME DisconnectTime; 405 | public FILETIME LastInputTime; 406 | public FILETIME LoginTime; 407 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1096)] 408 | private byte[] Reserved3; 409 | public FILETIME CurrentTime; 410 | } 411 | 412 | public enum WINSTATIONINFOCLASS 413 | { 414 | WinStationInformation = 8 415 | } 416 | 417 | public enum WTS_CONNECTSTATE_CLASS 418 | { 419 | Active, 420 | Connected, 421 | ConnectQuery, 422 | Shadow, 423 | Disconnected, 424 | Idle, 425 | Listen, 426 | Reset, 427 | Down, 428 | Init 429 | } 430 | 431 | public enum WTS_INFO_CLASS 432 | { 433 | SessionId=4, 434 | UserName, 435 | SessionName, 436 | DomainName, 437 | ConnectState, 438 | ClientBuildNumber, 439 | ClientName, 440 | ClientDirectory, 441 | ClientProtocolType=16 442 | } 443 | 444 | private static IntPtr OpenServer(string Name) 445 | { 446 | IntPtr server = WTSOpenServer(Name); 447 | return server; 448 | } 449 | 450 | private static void CloseServer(IntPtr ServerHandle) 451 | { 452 | WTSCloseServer(ServerHandle); 453 | } 454 | 455 | private static IList PtrToStructureList(IntPtr ppList, int count) where T : struct 456 | { 457 | List result = new List(); 458 | long pointer = ppList.ToInt64(); 459 | int sizeOf = Marshal.SizeOf(typeof(T)); 460 | 461 | for (int index = 0; index < count; index++) 462 | { 463 | T item = (T) Marshal.PtrToStructure(new IntPtr(pointer), typeof(T)); 464 | result.Add(item); 465 | pointer += sizeOf; 466 | } 467 | return result; 468 | } 469 | 470 | public static DateTime? FileTimeToDateTime(FILETIME ft) 471 | { 472 | if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0) 473 | { 474 | return null; 475 | } 476 | long hFT = (((long) ft.dwHighDateTime) << 32) + ft.dwLowDateTime; 477 | return DateTime.FromFileTime(hFT); 478 | } 479 | 480 | public static WINSTATIONINFORMATIONW GetWinStationInformation(IntPtr server, int sessionId) 481 | { 482 | int retLen = 0; 483 | WINSTATIONINFORMATIONW wsInfo = new WINSTATIONINFORMATIONW(); 484 | WinStationQueryInformation(server, sessionId, (int) WINSTATIONINFOCLASS.WinStationInformation, ref wsInfo, Marshal.SizeOf(typeof(WINSTATIONINFORMATIONW)), ref retLen); 485 | return wsInfo; 486 | } 487 | 488 | public static TerminalSessionData[] ListSessions(string ServerName) 489 | { 490 | IntPtr server = IntPtr.Zero; 491 | if (ServerName == "localhost" || ServerName == String.Empty) 492 | { 493 | ServerName = Environment.MachineName; 494 | } 495 | 496 | List results = new List(); 497 | 498 | try 499 | { 500 | server = OpenServer(ServerName); 501 | IntPtr ppSessionInfo = IntPtr.Zero; 502 | int count; 503 | bool _isUserSession = false; 504 | IList sessionsInfo; 505 | 506 | if (WTSEnumerateSessions(server, 0, 1, out ppSessionInfo, out count) == 0) 507 | { 508 | throw new Win32Exception(); 509 | } 510 | 511 | try 512 | { 513 | sessionsInfo = PtrToStructureList(ppSessionInfo, count); 514 | } 515 | finally 516 | { 517 | WTSFreeMemory(ppSessionInfo); 518 | } 519 | 520 | foreach (WTS_SESSION_INFO sessionInfo in sessionsInfo) 521 | { 522 | if (sessionInfo.SessionName != "Services" && sessionInfo.SessionName != "RDP-Tcp") 523 | { 524 | _isUserSession = true; 525 | } 526 | results.Add(new TerminalSessionData(sessionInfo.SessionId, sessionInfo.State.ToString(), sessionInfo.SessionName, _isUserSession)); 527 | _isUserSession = false; 528 | } 529 | } 530 | finally 531 | { 532 | CloseServer(server); 533 | } 534 | 535 | TerminalSessionData[] returnData = results.ToArray(); 536 | return returnData; 537 | } 538 | 539 | public static TerminalSessionInfo GetSessionInfo(string ServerName, int SessionId) 540 | { 541 | IntPtr server = IntPtr.Zero; 542 | IntPtr buffer = IntPtr.Zero; 543 | int bytesReturned; 544 | TerminalSessionInfo data = new TerminalSessionInfo(); 545 | bool _IsCurrentSessionId = false; 546 | bool _IsConsoleSession = false; 547 | bool _IsUserSession = false; 548 | int currentSessionID = 0; 549 | string _NTAccount = String.Empty; 550 | if (ServerName == "localhost" || ServerName == String.Empty) 551 | { 552 | ServerName = Environment.MachineName; 553 | } 554 | if (ProcessIdToSessionId(GetCurrentProcessId(), ref currentSessionID) == false) 555 | { 556 | currentSessionID = -1; 557 | } 558 | 559 | // Get all members of the local administrators group 560 | bool _IsLocalAdminCheckSuccess = false; 561 | List localAdminGroupSidsList = new List(); 562 | try 563 | { 564 | DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + ServerName + ",Computer"); 565 | string localAdminGroupName = new SecurityIdentifier("S-1-5-32-544").Translate(typeof(NTAccount)).Value.Split('\\')[1]; 566 | DirectoryEntry admGroup = localMachine.Children.Find(localAdminGroupName, "group"); 567 | object members = admGroup.Invoke("members", null); 568 | foreach (object groupMember in (IEnumerable)members) 569 | { 570 | DirectoryEntry member = new DirectoryEntry(groupMember); 571 | if (member.Name != String.Empty) 572 | { 573 | localAdminGroupSidsList.Add((new NTAccount(member.Name)).Translate(typeof(SecurityIdentifier)).Value); 574 | } 575 | } 576 | _IsLocalAdminCheckSuccess = true; 577 | } 578 | catch { } 579 | 580 | try 581 | { 582 | server = OpenServer(ServerName); 583 | 584 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientBuildNumber, out buffer, out bytesReturned) == false) 585 | { 586 | return data; 587 | } 588 | int lData = Marshal.ReadInt32(buffer); 589 | data.ClientBuildNumber = lData; 590 | 591 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientDirectory, out buffer, out bytesReturned) == false) 592 | { 593 | return data; 594 | } 595 | string strData = Marshal.PtrToStringAnsi(buffer); 596 | data.ClientDirectory = strData; 597 | 598 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientName, out buffer, out bytesReturned) == false) 599 | { 600 | return data; 601 | } 602 | strData = Marshal.PtrToStringAnsi(buffer); 603 | data.ClientName = strData; 604 | 605 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientProtocolType, out buffer, out bytesReturned) == false) 606 | { 607 | return data; 608 | } 609 | Int16 intData = Marshal.ReadInt16(buffer); 610 | if (intData == 2) 611 | { 612 | strData = "RDP"; 613 | data.IsRdpSession = true; 614 | } 615 | else 616 | { 617 | strData = ""; 618 | data.IsRdpSession = false; 619 | } 620 | data.ClientProtocolType = strData; 621 | 622 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ConnectState, out buffer, out bytesReturned) == false) 623 | { 624 | return data; 625 | } 626 | lData = Marshal.ReadInt32(buffer); 627 | data.ConnectState = ((WTS_CONNECTSTATE_CLASS) lData).ToString(); 628 | 629 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionId, out buffer, out bytesReturned) == false) 630 | { 631 | return data; 632 | } 633 | lData = Marshal.ReadInt32(buffer); 634 | data.SessionId = lData; 635 | 636 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.DomainName, out buffer, out bytesReturned) == false) 637 | { 638 | return data; 639 | } 640 | strData = Marshal.PtrToStringAnsi(buffer).ToUpper(); 641 | data.DomainName = strData; 642 | if (strData != String.Empty) 643 | { 644 | _NTAccount = strData; 645 | } 646 | 647 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.UserName, out buffer, out bytesReturned) == false) 648 | { 649 | return data; 650 | } 651 | strData = Marshal.PtrToStringAnsi(buffer); 652 | data.UserName = strData; 653 | if (strData != String.Empty) 654 | { 655 | data.NTAccount = _NTAccount + "\\" + strData; 656 | string _Sid = (new NTAccount(_NTAccount + "\\" + strData)).Translate(typeof(SecurityIdentifier)).Value; 657 | data.SID = _Sid; 658 | if (_IsLocalAdminCheckSuccess == true) 659 | { 660 | foreach (string localAdminGroupSid in localAdminGroupSidsList) 661 | { 662 | if (localAdminGroupSid == _Sid) 663 | { 664 | data.IsLocalAdmin = true; 665 | break; 666 | } 667 | else 668 | { 669 | data.IsLocalAdmin = false; 670 | } 671 | } 672 | } 673 | } 674 | 675 | if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionName, out buffer, out bytesReturned) == false) 676 | { 677 | return data; 678 | } 679 | strData = Marshal.PtrToStringAnsi(buffer); 680 | data.SessionName = strData; 681 | if (strData != "Services" && strData != "RDP-Tcp" && data.UserName != String.Empty) 682 | { 683 | _IsUserSession = true; 684 | } 685 | data.IsUserSession = _IsUserSession; 686 | if (strData == "Console") 687 | { 688 | _IsConsoleSession = true; 689 | } 690 | data.IsConsoleSession = _IsConsoleSession; 691 | 692 | WINSTATIONINFORMATIONW wsInfo = GetWinStationInformation(server, SessionId); 693 | DateTime? _loginTime = FileTimeToDateTime(wsInfo.LoginTime); 694 | DateTime? _lastInputTime = FileTimeToDateTime(wsInfo.LastInputTime); 695 | DateTime? _disconnectTime = FileTimeToDateTime(wsInfo.DisconnectTime); 696 | DateTime? _currentTime = FileTimeToDateTime(wsInfo.CurrentTime); 697 | TimeSpan? _idleTime = (_currentTime != null && _lastInputTime != null) ? _currentTime.Value - _lastInputTime.Value : TimeSpan.Zero; 698 | data.LogonTime = _loginTime; 699 | data.IdleTime = _idleTime; 700 | data.DisconnectTime = _disconnectTime; 701 | 702 | if (currentSessionID == SessionId) 703 | { 704 | _IsCurrentSessionId = true; 705 | } 706 | data.IsCurrentSession = _IsCurrentSessionId; 707 | } 708 | finally 709 | { 710 | WTSFreeMemory(buffer); 711 | buffer = IntPtr.Zero; 712 | CloseServer(server); 713 | } 714 | return data; 715 | } 716 | 717 | public static TerminalSessionInfo[] GetUserSessionInfo(string ServerName) 718 | { 719 | if (ServerName == "localhost" || ServerName == String.Empty) 720 | { 721 | ServerName = Environment.MachineName; 722 | } 723 | 724 | // Find and get detailed information for all user sessions 725 | // Also determine the active user session. If a console user exists, then that will be the active user session. 726 | // If no console user exists but users are logged in, such as on terminal servers, then select the first logged-in non-console user that is either 'Active' or 'Connected' as the active user. 727 | TerminalSessionData[] sessions = ListSessions(ServerName); 728 | TerminalSessionInfo sessionInfo = new TerminalSessionInfo(); 729 | List userSessionsInfo = new List(); 730 | string firstActiveUserNTAccount = String.Empty; 731 | bool IsActiveUserSessionSet = false; 732 | foreach (TerminalSessionData session in sessions) 733 | { 734 | if (session.IsUserSession == true) 735 | { 736 | sessionInfo = GetSessionInfo(ServerName, session.SessionId); 737 | if (sessionInfo.IsUserSession == true) 738 | { 739 | if ((firstActiveUserNTAccount == String.Empty) && (sessionInfo.ConnectState == "Active" || sessionInfo.ConnectState == "Connected")) 740 | { 741 | firstActiveUserNTAccount = sessionInfo.NTAccount; 742 | } 743 | 744 | if (sessionInfo.IsConsoleSession == true) 745 | { 746 | sessionInfo.IsActiveUserSession = true; 747 | IsActiveUserSessionSet = true; 748 | } 749 | else 750 | { 751 | sessionInfo.IsActiveUserSession = false; 752 | } 753 | 754 | userSessionsInfo.Add(sessionInfo); 755 | } 756 | } 757 | } 758 | 759 | TerminalSessionInfo[] userSessions = userSessionsInfo.ToArray(); 760 | if (IsActiveUserSessionSet == false) 761 | { 762 | foreach (TerminalSessionInfo userSession in userSessions) 763 | { 764 | if (userSession.NTAccount == firstActiveUserNTAccount) 765 | { 766 | userSession.IsActiveUserSession = true; 767 | break; 768 | } 769 | } 770 | } 771 | 772 | return userSessions; 773 | } 774 | } 775 | } 776 | -------------------------------------------------------------------------------- /Toolkit/Deploy-Application.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDegreef/PSADT_GUI/7a9a0168a74ba484552ae44761b79bcc610d2b24/Toolkit/Deploy-Application.exe -------------------------------------------------------------------------------- /Toolkit/Deploy-Application.exe.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Toolkit/Deploy-Application.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script performs the installation or uninstallation of an application(s). 4 | .DESCRIPTION 5 | The script is provided as a template to perform an install or uninstall of an application(s). 6 | The script either performs an "Install" deployment type or an "Uninstall" deployment type. 7 | The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install. 8 | The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application. 9 | .PARAMETER DeploymentType 10 | The type of deployment to perform. Default is: Install. 11 | .PARAMETER DeployMode 12 | Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive. 13 | .PARAMETER AllowRebootPassThru 14 | Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered. 15 | .PARAMETER TerminalServerMode 16 | Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers. 17 | .PARAMETER DisableLogging 18 | Disables logging to file for the script. Default is: $false. 19 | .EXAMPLE 20 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeployMode 'Silent'; Exit $LastExitCode }" 21 | .EXAMPLE 22 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -AllowRebootPassThru; Exit $LastExitCode }" 23 | .EXAMPLE 24 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeploymentType 'Uninstall'; Exit $LastExitCode }" 25 | .EXAMPLE 26 | Deploy-Application.exe -DeploymentType "Install" -DeployMode "Silent" 27 | .NOTES 28 | Toolkit Exit Code Ranges: 29 | 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 30 | 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 31 | 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 32 | .LINK 33 | http://psappdeploytoolkit.com 34 | #> 35 | [CmdletBinding()] 36 | Param ( 37 | [Parameter(Mandatory=$false)] 38 | [ValidateSet('Install','Uninstall')] 39 | [string]$DeploymentType = 'Install', 40 | [Parameter(Mandatory=$false)] 41 | [ValidateSet('Interactive','Silent','NonInteractive')] 42 | [string]$DeployMode = 'Interactive', 43 | [Parameter(Mandatory=$false)] 44 | [switch]$AllowRebootPassThru = $false, 45 | [Parameter(Mandatory=$false)] 46 | [switch]$TerminalServerMode = $false, 47 | [Parameter(Mandatory=$false)] 48 | [switch]$DisableLogging = $false 49 | ) 50 | 51 | Try { 52 | ## Set the script execution policy for this process 53 | Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {} 54 | 55 | ##*=============================================== 56 | ##* VARIABLE DECLARATION 57 | ##*=============================================== 58 | ## Variables: Application 59 | [string]$appVendor = '' 60 | [string]$appName = '' 61 | [string]$appVersion = '' 62 | [string]$appArch = '' 63 | [string]$appLang = 'EN' 64 | [string]$appRevision = '01' 65 | [string]$appScriptVersion = '1.0.0' 66 | [string]$appScriptDate = '02/12/2017' 67 | [string]$appScriptAuthor = '' 68 | ##*=============================================== 69 | ## Variables: Install Titles (Only set here to override defaults set by the toolkit) 70 | [string]$installName = '' 71 | [string]$installTitle = '' 72 | 73 | ##* Do not modify section below 74 | #region DoNotModify 75 | 76 | ## Variables: Exit Code 77 | [int32]$mainExitCode = 0 78 | 79 | ## Variables: Script 80 | [string]$deployAppScriptFriendlyName = 'Deploy Application' 81 | [version]$deployAppScriptVersion = [version]'3.6.9' 82 | [string]$deployAppScriptDate = '02/12/2017' 83 | [hashtable]$deployAppScriptParameters = $psBoundParameters 84 | 85 | ## Variables: Environment 86 | If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation } 87 | [string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent 88 | 89 | ## Dot source the required App Deploy Toolkit Functions 90 | Try { 91 | [string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1" 92 | If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." } 93 | If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain } 94 | } 95 | Catch { 96 | If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 } 97 | Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue' 98 | ## Exit the script, returning the exit code to SCCM 99 | If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode } 100 | } 101 | 102 | #endregion 103 | ##* Do not modify section above 104 | ##*=============================================== 105 | ##* END VARIABLE DECLARATION 106 | ##*=============================================== 107 | 108 | If ($deploymentType -ine 'Uninstall') { 109 | ##*=============================================== 110 | ##* PRE-INSTALLATION 111 | ##*=============================================== 112 | [string]$installPhase = 'Pre-Installation' 113 | 114 | ## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt 115 | Show-InstallationWelcome -CloseApps 'iexplore' -AllowDefer -DeferTimes 3 -CheckDiskSpace -PersistPrompt 116 | 117 | ## Show Progress Message (with the default message) 118 | Show-InstallationProgress 119 | 120 | ## 121 | 122 | 123 | ##*=============================================== 124 | ##* INSTALLATION 125 | ##*=============================================== 126 | [string]$installPhase = 'Installation' 127 | 128 | ## Handle Zero-Config MSI Installations 129 | If ($useDefaultMsi) { 130 | [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Install'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } 131 | Execute-MSI @ExecuteDefaultMSISplat; If ($defaultMspFiles) { $defaultMspFiles | ForEach-Object { Execute-MSI -Action 'Patch' -Path $_ } } 132 | } 133 | 134 | ## 135 | 136 | 137 | ##*=============================================== 138 | ##* POST-INSTALLATION 139 | ##*=============================================== 140 | [string]$installPhase = 'Post-Installation' 141 | 142 | ## 143 | 144 | ## Display a message at the end of the install 145 | If (-not $useDefaultMsi) { Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install or remove it completely for unattended installations.' -ButtonRightText 'OK' -Icon Information -NoWait } 146 | } 147 | ElseIf ($deploymentType -ieq 'Uninstall') 148 | { 149 | ##*=============================================== 150 | ##* PRE-UNINSTALLATION 151 | ##*=============================================== 152 | [string]$installPhase = 'Pre-Uninstallation' 153 | 154 | ## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing 155 | Show-InstallationWelcome -CloseApps 'iexplore' -CloseAppsCountdown 60 156 | 157 | ## Show Progress Message (with the default message) 158 | Show-InstallationProgress 159 | 160 | ## 161 | 162 | 163 | ##*=============================================== 164 | ##* UNINSTALLATION 165 | ##*=============================================== 166 | [string]$installPhase = 'Uninstallation' 167 | 168 | ## Handle Zero-Config MSI Uninstallations 169 | If ($useDefaultMsi) { 170 | [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Uninstall'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } 171 | Execute-MSI @ExecuteDefaultMSISplat 172 | } 173 | 174 | # 175 | 176 | 177 | ##*=============================================== 178 | ##* POST-UNINSTALLATION 179 | ##*=============================================== 180 | [string]$installPhase = 'Post-Uninstallation' 181 | 182 | ## 183 | 184 | 185 | } 186 | 187 | ##*=============================================== 188 | ##* END SCRIPT BODY 189 | ##*=============================================== 190 | 191 | ## Call the Exit-Script function to perform final cleanup operations 192 | Exit-Script -ExitCode $mainExitCode 193 | } 194 | Catch { 195 | [int32]$mainExitCode = 60001 196 | [string]$mainErrorMessage = "$(Resolve-Error)" 197 | Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName 198 | Show-DialogBox -Text $mainErrorMessage -Icon 'Stop' 199 | Exit-Script -ExitCode $mainExitCode 200 | } -------------------------------------------------------------------------------- /config/Deploy-Application.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script performs the installation or uninstallation of an application(s). 4 | # LICENSE # 5 | PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. 6 | Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. 7 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . 9 | .DESCRIPTION 10 | The script is provided as a template to perform an install or uninstall of an application(s). 11 | The script either performs an "Install" deployment type or an "Uninstall" deployment type. 12 | The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install. 13 | The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application. 14 | .PARAMETER DeploymentType 15 | The type of deployment to perform. Default is: Install. 16 | .PARAMETER DeployMode 17 | Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive. 18 | .PARAMETER AllowRebootPassThru 19 | Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered. 20 | .PARAMETER TerminalServerMode 21 | Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers. 22 | .PARAMETER DisableLogging 23 | Disables logging to file for the script. Default is: $false. 24 | .EXAMPLE 25 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeployMode 'Silent'; Exit $LastExitCode }" 26 | .EXAMPLE 27 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -AllowRebootPassThru; Exit $LastExitCode }" 28 | .EXAMPLE 29 | powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeploymentType 'Uninstall'; Exit $LastExitCode }" 30 | .EXAMPLE 31 | Deploy-Application.exe -DeploymentType "Install" -DeployMode "Silent" 32 | .NOTES 33 | Toolkit Exit Code Ranges: 34 | 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 35 | 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 36 | 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 37 | .LINK 38 | http://psappdeploytoolkit.com 39 | #> 40 | [CmdletBinding()] 41 | Param ( 42 | [Parameter(Mandatory=$false)] 43 | [ValidateSet('Install','Uninstall')] 44 | [string]$DeploymentType = 'Install', 45 | [Parameter(Mandatory=$false)] 46 | [ValidateSet('Interactive','Silent','NonInteractive')] 47 | [string]$DeployMode = 'Interactive', 48 | [Parameter(Mandatory=$false)] 49 | [switch]$AllowRebootPassThru = $false, 50 | [Parameter(Mandatory=$false)] 51 | [switch]$TerminalServerMode = $false, 52 | [Parameter(Mandatory=$false)] 53 | [switch]$DisableLogging = $false 54 | ) 55 | 56 | Try { 57 | ## Set the script execution policy for this process 58 | Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {} 59 | 60 | ##*=============================================== 61 | ##* VARIABLE DECLARATION 62 | ##*=============================================== 63 | ## Variables: Application 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ##*=============================================== 77 | ## Variables: Install Titles (Only set here to override defaults set by the toolkit) 78 | [string]$installName = '' 79 | [string]$installTitle = '' 80 | 81 | ##* Do not modify section below 82 | #region DoNotModify 83 | 84 | ## Variables: Exit Code 85 | [int32]$mainExitCode = 0 86 | 87 | ## Variables: Script 88 | [string]$deployAppScriptFriendlyName = 'Deploy Application' 89 | [version]$deployAppScriptVersion = [version]'3.7.0' 90 | [string]$deployAppScriptDate = '02/13/2018' 91 | [hashtable]$deployAppScriptParameters = $psBoundParameters 92 | 93 | ## Variables: Environment 94 | If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation } 95 | [string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent 96 | 97 | ## Dot source the required App Deploy Toolkit Functions 98 | Try { 99 | [string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1" 100 | If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." } 101 | If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain } 102 | } 103 | Catch { 104 | If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 } 105 | Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue' 106 | ## Exit the script, returning the exit code to SCCM 107 | If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode } 108 | } 109 | 110 | #endregion 111 | ##* Do not modify section above 112 | ##*=============================================== 113 | ##* END VARIABLE DECLARATION 114 | ##*=============================================== 115 | 116 | If ($deploymentType -ine 'Uninstall') { 117 | ##*=============================================== 118 | ##* PRE-INSTALLATION 119 | ##*=============================================== 120 | [string]$installPhase = 'Pre-Installation' 121 | 122 | ## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt 123 | Show-InstallationWelcome -CloseApps 'iexplore' -AllowDefer -DeferTimes 3 -CheckDiskSpace -PersistPrompt 124 | 125 | ## Show Progress Message (with the default message) 126 | Show-InstallationProgress 127 | 128 | ## 129 | 130 | 131 | ##*=============================================== 132 | ##* INSTALLATION 133 | ##*=============================================== 134 | [string]$installPhase = 'Installation' 135 | 136 | ## Handle Zero-Config MSI Installations 137 | If ($useDefaultMsi) { 138 | [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Install'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } 139 | Execute-MSI @ExecuteDefaultMSISplat; If ($defaultMspFiles) { $defaultMspFiles | ForEach-Object { Execute-MSI -Action 'Patch' -Path $_ } } 140 | } 141 | 142 | ## 143 | 144 | 145 | 146 | ##*=============================================== 147 | ##* POST-INSTALLATION 148 | ##*=============================================== 149 | [string]$installPhase = 'Post-Installation' 150 | 151 | ## 152 | 153 | 154 | ## Display a message at the end of the install 155 | If (-not $useDefaultMsi) { 156 | 157 | } 158 | } 159 | ElseIf ($deploymentType -ieq 'Uninstall') 160 | { 161 | ##*=============================================== 162 | ##* PRE-UNINSTALLATION 163 | ##*=============================================== 164 | [string]$installPhase = 'Pre-Uninstallation' 165 | 166 | ## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing 167 | #Show-InstallationWelcome -CloseApps 'iexplore' -CloseAppsCountdown 60 168 | 169 | ## Show Progress Message (with the default message) 170 | Show-InstallationProgress 171 | 172 | ## 173 | 174 | 175 | ##*=============================================== 176 | ##* UNINSTALLATION 177 | ##*=============================================== 178 | [string]$installPhase = 'Uninstallation' 179 | 180 | ## Handle Zero-Config MSI Uninstallations 181 | If ($useDefaultMsi) { 182 | [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Uninstall'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } 183 | Execute-MSI @ExecuteDefaultMSISplat 184 | } 185 | 186 | # 187 | 188 | 189 | 190 | ##*=============================================== 191 | ##* POST-UNINSTALLATION 192 | ##*=============================================== 193 | [string]$installPhase = 'Post-Uninstallation' 194 | 195 | ## 196 | 197 | 198 | } 199 | 200 | ##*=============================================== 201 | ##* END SCRIPT BODY 202 | ##*=============================================== 203 | 204 | ## Call the Exit-Script function to perform final cleanup operations 205 | Exit-Script -ExitCode $mainExitCode 206 | } 207 | Catch { 208 | [int32]$mainExitCode = 60001 209 | [string]$mainErrorMessage = "$(Resolve-Error)" 210 | Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName 211 | Show-DialogBox -Text $mainErrorMessage -Icon 'Stop' 212 | Exit-Script -ExitCode $mainExitCode 213 | } -------------------------------------------------------------------------------- /config/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |