├── LICENSE ├── PeerCacheExplorer.ps1 ├── README.md ├── Resources ├── ControlzEx.dll ├── MahApps.Metro.dll └── System.Windows.Interactivity.dll ├── Scripts ├── Loading.ps1 ├── POWmi │ ├── License.txt │ ├── POWmi.psd1 │ ├── POWmi.psm1 │ ├── Private │ │ ├── ConvertFrom-Base64StringToObject.ps1 │ │ ├── ConvertFrom-CLIXml.ps1 │ │ ├── ConvertTo-Base64StringFromObject.ps1 │ │ └── ConvertTo-CliXML.ps1 │ └── Public │ │ └── Invoke-POWmi.ps1 ├── WiringAboutDialog.ps1 ├── WiringMainWindow.ps1 └── WiringSettingsDialog.ps1 └── XAML ├── AboutDialog.xaml ├── MainWindow.xaml └── SettingsDialog.xaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nathan ziehnert 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 | -------------------------------------------------------------------------------- /PeerCacheExplorer.ps1: -------------------------------------------------------------------------------- 1 | # PoSHPF - Version 1.2 (Modified) 2 | # Grab all resources (MahApps, etc), all XAML files, and any potential static resources 3 | $Global:resources = Get-ChildItem -Path "$PSScriptRoot\Resources\*.dll" -ErrorAction SilentlyContinue 4 | $Global:XAML = Get-ChildItem -Path "$PSScriptRoot\XAML\*.xaml" -Recurse -ErrorAction SilentlyContinue 5 | $Global:MediaResources = Get-ChildItem -Path "$PSScriptRoot\Media" -ErrorAction SilentlyContinue 6 | 7 | # This class allows the synchronized hashtable to be available across threads, 8 | # but also passes a couple of methods along with it to do GUI things via the 9 | # object's dispatcher. 10 | class SyncClass 11 | { 12 | #Hashtable containing all forms/windows and controls - automatically created when newing up 13 | [hashtable]$SyncHash = [hashtable]::Synchronized(@{}) 14 | 15 | # method to close the window - pass window name 16 | [void]CloseWindow($windowName){ 17 | $this.SyncHash.$windowName.Dispatcher.Invoke([action]{$this.SyncHash.$windowName.Close()},"Normal") 18 | } 19 | 20 | # method to update GUI - pass object name, property and value 21 | [void]UpdateElement($object,$property,$value){ 22 | $this.SyncHash.$object.Dispatcher.Invoke([action]{ $this.SyncHash.$object.$property = $value },"Normal") 23 | } 24 | 25 | # method to get property value when running in background scriptblock 26 | [object]GetControlPropertyValue($object,$properties) 27 | { 28 | $command = '$this.SyncHash.$object' 29 | if($properties){$properties | %{$command += ".$_"}} 30 | $this.SyncHash.$object.Dispatcher.Invoke([action]{ $this.SyncHash.TempPropertyValue = (Invoke-Expression -Command $command) },"Normal") 31 | return $this.SyncHash.TempPropertyValue 32 | } 33 | 34 | # method to run control method - pass object name, method, and if needed parameters in an array 35 | [void]RunControlMethod($object,$properties,$method){ 36 | $command = '$this.SyncHash.$object' 37 | if($properties){$properties | %{$command += ".$_"}} 38 | $command += '.$method()' 39 | $this.SyncHash.$object.Dispatcher.Invoke([action]{ Invoke-Expression -Command $command },"Normal") 40 | } 41 | [void]RunControlMethod($object,$properties,$method,$parameters){ 42 | $command = '$this.SyncHash.$object' 43 | if($properties){$properties | %{$command += ".$_"}} 44 | $command += '.$method(' 45 | for($i=0;$i -lt $parameters.Count;$i++) 46 | { 47 | $command += "`$parameters[$i]" 48 | if(($parameters.Count - $i) -ne 1) 49 | { 50 | $command += "," 51 | } 52 | } 53 | $command += ")" 54 | $this.SyncHash.$object.Dispatcher.Invoke([action]{ Invoke-Expression -Command $command },"Normal") 55 | } 56 | } 57 | $Global:SyncClass = [SyncClass]::new() # create a new instance of this SyncClass to use. 58 | 59 | ################### 60 | ## Import Resources 61 | ################### 62 | # Load WPF Assembly 63 | Add-Type -assemblyName PresentationFramework 64 | 65 | # Load Resources 66 | foreach($dll in $resources) { [System.Reflection.Assembly]::LoadFrom("$($dll.FullName)") | out-null } 67 | 68 | ############## 69 | ## Import XAML 70 | ############## 71 | $xp = '[^a-zA-Z_0-9]' # All characters that are not a-Z, 0-9, or _ 72 | $vx = @() # An array of XAML files loaded 73 | 74 | foreach($x in $XAML) { 75 | # Items from XAML that are known to cause issues 76 | # when PowerShell parses them. 77 | $xamlToRemove = @( 78 | 'mc:Ignorable="d"', 79 | "x:Class=`"(.*?)`"", 80 | "xmlns:local=`"(.*?)`"", 81 | "d:designHeight=`"(.*?)`"", 82 | "d:designWidth=`"(.*?)`"" 83 | ) 84 | 85 | $xaml = Get-Content $x.FullName # Load XAML 86 | $xaml = $xaml -replace "x:N",'N' # Rename x:Name to just Name (for consumption in variables later) 87 | foreach($xtr in $xamlToRemove){ $xaml = $xaml -replace $xtr } # Remove items from $xamlToRemove 88 | 89 | # Create a new variable to store the XAML as XML 90 | New-Variable -Name "xaml$(($x.BaseName) -replace $xp, '_')" -Value ($xaml -as [xml]) -Force 91 | 92 | # Add XAML to list of XAML documents processed 93 | $vx += "$(($x.BaseName) -replace $xp, '_')" 94 | } 95 | 96 | ####################### 97 | ## Add Media Resources 98 | ####################### 99 | $imageFileTypes = @(".jpg",".bmp",".gif",".tif",".png",".ico") # Supported image filetypes 100 | $avFileTypes = @(".mp3",".wav",".wmv") # Supported audio/visual filetypes 101 | $xp = '[^a-zA-Z_0-9]' # All characters that are not a-Z, 0-9, or _ 102 | if($MediaResources.Count -gt 0){ 103 | ## Okay... the following code is just silly. I know 104 | ## but hear me out. Adding the nodes to the elements 105 | ## directly caused big issues - mainly surrounding the 106 | ## "x:" namespace identifiers. This is a hacky fix but 107 | ## it does the trick. 108 | foreach($v in $vx) 109 | { 110 | $xml = ((Get-Variable -Name "xaml$($v)").Value) # Load the XML 111 | 112 | # add the resources needed for strings 113 | $xml.DocumentElement.SetAttribute("xmlns:sys","clr-namespace:System;assembly=System") 114 | 115 | # if the document doesn't already have a "Window.Resources" create it 116 | if($xml.FirstChild.Name -like "*Window*") 117 | { 118 | if($null -eq ($xml.DocumentElement.'Window.Resources')){ 119 | $fragment = "" 120 | $fragment += "" 121 | } 122 | else 123 | { 124 | $fragment = "" 125 | } 126 | } 127 | else 128 | { 129 | $fragment = "<$($xml.FirstChild.Name).Resources>" 130 | $fragment += "" 131 | } 132 | 133 | # Add each StaticResource with the key of the base name and source to the full name 134 | foreach($sr in $MediaResources) 135 | { 136 | $srname = "$($sr.BaseName -replace $xp, '_')$($sr.Extension.Substring(1).ToUpper())" #convert name to basename + Uppercase Extension 137 | if($sr.Extension -in $imageFileTypes){ $fragment += "" } 138 | if($sr.Extension -in $avFileTypes){ 139 | $uri = [System.Uri]::new($sr.FullName) 140 | $fragment += "$uri" 141 | } 142 | } 143 | 144 | # if the document doesn't already have a "Window.Resources" close it 145 | if($xml.FirstChild.Name -like "*Window*") 146 | { 147 | if($null -eq ($xml.DocumentElement.'Window.Resources')){ 148 | $fragment += "" 149 | $fragment += "" 150 | $xml.DocumentElement.InnerXml = $fragment + $xml.DocumentElement.InnerXml 151 | } 152 | else 153 | { 154 | $xml.DocumentElement.'Window.Resources'.ResourceDictionary.InnerXml += $fragment 155 | } 156 | } 157 | else 158 | { 159 | $fragment += "" 160 | $fragment += "" 161 | $xml.DocumentElement.InnerXml = $fragment + $xml.DocumentElement.InnerXml 162 | } 163 | 164 | # Reset the value of the variable 165 | (Get-Variable -Name "xaml$($v)").Value = $xml 166 | } 167 | } 168 | 169 | ################# 170 | ## Create "Forms" 171 | ################# 172 | $forms = @() 173 | foreach($x in $vx) 174 | { 175 | $Reader = (New-Object System.Xml.XmlNodeReader ((Get-Variable -Name "xaml$($x)").Value)) #load the xaml we created earlier into XmlNodeReader 176 | New-Variable -Name "form$($x)" -Value ([Windows.Markup.XamlReader]::Load($Reader)) -Force #load the xaml into XamlReader 177 | $forms += "form$($x)" #add the form name to our array 178 | $SyncClass.SyncHash.Add("form$($x)", (Get-Variable -Name "form$($x)").Value) #add the form object to our synched hashtable 179 | } 180 | 181 | ################################# 182 | ## Create Controls (Buttons, etc) 183 | ################################# 184 | $controls = @() 185 | $xp = '[^a-zA-Z_0-9]' # All characters that are not a-Z, 0-9, or _ 186 | foreach($x in $vx) 187 | { 188 | $xaml = (Get-Variable -Name "xaml$($x)").Value #load the xaml we created earlier 189 | $xaml.SelectNodes("//*[@Name]") | %{ #find all nodes with a "Name" attribute 190 | $cname = "form$($x)Control$(($_.Name -replace $xp, '_'))" 191 | Set-Variable -Name "$cname" -Value $SyncClass.SyncHash."form$($x)".FindName($_.Name) #create a variale to hold the control/object 192 | $controls += (Get-Variable -Name "form$($x)Control$($_.Name)").Name #add the control name to our array 193 | $SyncClass.SyncHash.Add($cname, $SyncClass.SyncHash."form$($x)".FindName($_.Name)) #add the control directly to the hashtable 194 | } 195 | } 196 | 197 | ############################ 198 | ## FORMS AND CONTROLS OUTPUT 199 | ############################ 200 | Write-Host -ForegroundColor Cyan "The following forms were created:" 201 | $forms | %{ Write-Host -ForegroundColor Yellow " `$$_"} #output all forms to screen 202 | if($controls.Count -gt 0){ 203 | Write-Host "" 204 | Write-Host -ForegroundColor Cyan "The following controls were created:" 205 | $controls | %{ Write-Host -ForegroundColor Yellow " `$$_"} #output all named controls to screen 206 | } 207 | 208 | ####################### 209 | ## DISABLE A/V AUTOPLAY 210 | ####################### 211 | foreach($x in $vx) 212 | { 213 | $carray = @() 214 | $fts = $syncClass.SyncHash."form$($x)" 215 | foreach($c in $fts.Content.Children) 216 | { 217 | if($c.GetType().Name -eq "MediaElement") #find all controls with the type MediaElement 218 | { 219 | $c.LoadedBehavior = "Manual" #Don't autoplay 220 | $c.UnloadedBehavior = "Stop" #When the window closes, stop the music 221 | $carray += $c #add the control to an array 222 | } 223 | } 224 | if($carray.Count -gt 0) 225 | { 226 | New-Variable -Name "form$($x)PoSHPFCleanupAudio" -Value $carray -Force # Store the controls in an array to be accessed later 227 | $syncClass.SyncHash."form$($x)".Add_Closed({ 228 | foreach($c in (Get-Variable "form$($x)PoSHPFCleanupAudio").Value) 229 | { 230 | $c.Source = $null #stops any currently playing media 231 | } 232 | }) 233 | } 234 | } 235 | 236 | ##################### 237 | ## RUNSPACE FUNCTIONS 238 | ##################### 239 | ## Yo dawg... Runspace to clean up Runspaces 240 | ## Thank you Boe Prox / Stephen Owen 241 | #region RSCleanup 242 | $Script:JobCleanup = [hashtable]::Synchronized(@{}) 243 | $Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList)) #hashtable to store all these runspaces 244 | 245 | $jobCleanup.Flag = $True #cleanup jobs 246 | $newRunspace =[runspacefactory]::CreateRunspace() #create a new runspace for this job to cleanup jobs to live 247 | $newRunspace.ApartmentState = "STA" 248 | $newRunspace.ThreadOptions = "ReuseThread" 249 | $newRunspace.Open() 250 | $newRunspace.SessionStateProxy.SetVariable("jobCleanup",$jobCleanup) #pass the jobCleanup variable to the runspace 251 | $newRunspace.SessionStateProxy.SetVariable("jobs",$jobs) #pass the jobs variable to the runspace 252 | $jobCleanup.PowerShell = [PowerShell]::Create().AddScript({ 253 | #Routine to handle completed runspaces 254 | Do { 255 | Foreach($runspace in $jobs) { 256 | If ($runspace.Runspace.isCompleted) { #if runspace is complete 257 | [void]$runspace.powershell.EndInvoke($runspace.Runspace) #then end the script 258 | $runspace.powershell.dispose() #dispose of the memory 259 | $runspace.Runspace = $null #additional garbage collection 260 | $runspace.powershell = $null #additional garbage collection 261 | } 262 | } 263 | #Clean out unused runspace jobs 264 | $temphash = $jobs.clone() 265 | $temphash | Where { 266 | $_.runspace -eq $Null 267 | } | ForEach { 268 | $jobs.remove($_) 269 | } 270 | Start-Sleep -Seconds 1 #lets not kill the processor here 271 | } while ($jobCleanup.Flag) 272 | }) 273 | $jobCleanup.PowerShell.Runspace = $newRunspace 274 | $jobCleanup.Thread = $jobCleanup.PowerShell.BeginInvoke() 275 | #endregion RSCleanup 276 | 277 | #This function creates a new runspace for a script block to execute 278 | #so that you can do your long running tasks not in the UI thread. 279 | #Also the SyncClass is passed to this runspace so you can do UI 280 | #updates from this thread as well. 281 | function Start-BackgroundScriptBlock($scriptBlock){ 282 | $newRunspace =[runspacefactory]::CreateRunspace() 283 | $newRunspace.ApartmentState = "STA" 284 | $newRunspace.ThreadOptions = "ReuseThread" 285 | $newRunspace.Open() 286 | $newRunspace.SessionStateProxy.SetVariable("SyncClass",$SyncClass) 287 | $PowerShell = [PowerShell]::Create().AddScript($scriptBlock) 288 | $PowerShell.Runspace = $newRunspace 289 | 290 | #Add it to the job list so that we can make sure it is cleaned up 291 | [void]$Jobs.Add( 292 | [pscustomobject]@{ 293 | PowerShell = $PowerShell 294 | Runspace = $PowerShell.BeginInvoke() 295 | } 296 | ) 297 | } 298 | 299 | ######################## 300 | ## WIRE UP YOUR CONTROLS 301 | ######################## 302 | $SyncClass.SyncHash.ScriptRoot = $PSScriptRoot #Share the ScriptRoot with Background Threads 303 | . "$PSScriptRoot\Scripts\Loading.ps1" #On load / refresh 304 | . "$PSScriptRoot\Scripts\WiringAboutDialog.ps1" #Wire up the about dialog 305 | . "$PSScriptRoot\Scripts\WiringSettingsDialog.ps1" #Wire up the settings dialog 306 | . "$PSScriptRoot\Scripts\WiringMainWindow.ps1" #Wire up the main window 307 | 308 | ############################ 309 | ###### DISPLAY DIALOG ###### 310 | ############################ 311 | [void]$formMainWindow.ShowDialog() 312 | 313 | ########################## 314 | ##### SCRIPT CLEANUP ##### 315 | ########################## 316 | $jobCleanup.Flag = $false #Stop Cleaning Jobs 317 | $jobCleanup.PowerShell.Runspace.Close() #Close the runspace 318 | $jobCleanup.PowerShell.Dispose() #Remove the runspace from memory 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PeerCacheExplorer 2 | A tool to review and possibly remove peer cache content from SuperPeers 3 | 4 | More information here: 5 | https://z-nerd.com/blog/2019/11/01-peercache-explorer/ 6 | -------------------------------------------------------------------------------- /Resources/ControlzEx.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theznerd/PeerCacheExplorer/c3452a7593751bf701d6cd74d74aa72460f8f413/Resources/ControlzEx.dll -------------------------------------------------------------------------------- /Resources/MahApps.Metro.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theznerd/PeerCacheExplorer/c3452a7593751bf701d6cd74d74aa72460f8f413/Resources/MahApps.Metro.dll -------------------------------------------------------------------------------- /Resources/System.Windows.Interactivity.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theznerd/PeerCacheExplorer/c3452a7593751bf701d6cd74d74aa72460f8f413/Resources/System.Windows.Interactivity.dll -------------------------------------------------------------------------------- /Scripts/Loading.ps1: -------------------------------------------------------------------------------- 1 | # The general refresh script 2 | $RefreshSB = { 3 | # Disable refresh button and show loading overlay 4 | $formMainWindowControlLoadingOverlay.Visibility = "Visible" 5 | $formMainWindowControlRefreshButton.IsEnabled = $false 6 | 7 | # Disable the content buttons (validate comes later) 8 | #$formMainWindowControlValidateContent.IsEnabled = $false 9 | $formMainWindowControlRemoveContent.IsEnabled = $false 10 | 11 | # Don't Act on Selection Changed (see WiringMainWindow.ps1) 12 | $SyncClass.SyncHash.ActOnAny = $false 13 | $SyncClass.SyncHash.DataGridList.Clear() # Clear anything in the datagrid already 14 | 15 | # Initialize Variables 16 | # SuperPeer List 17 | $formMainWindowControlLoadingText.Content = "Initializing Variables..." 18 | $SyncClass.SyncHash.SuperPeerList = New-Object System.Collections.ObjectModel.ObservableCollection[Object] 19 | $SyncClass.SyncHash.SuperPeerListLock = New-Object PSObject 20 | $SyncClass.SyncHash.SuperPeerView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($SyncClass.SyncHash.SuperPeerList) 21 | $formMainWindowControlSuperPeerListBox.DisplayMemberPath = 'Name00' 22 | $formMainWindowControlSuperPeerListBox.ItemsSource = $SyncClass.SyncHash.SuperPeerView 23 | 24 | # Create a binding to pair the listbox to the observable collection 25 | $SuperPeerListBinding = New-Object System.Windows.Data.Binding 26 | $SuperPeerListBinding.Source = $SyncClass.SyncHash.SuperPeerList 27 | $SuperPeerListBinding.Mode = [System.Windows.Data.BindingMode]::OneWay 28 | [void][System.Windows.Data.BindingOperations]::EnableCollectionSynchronization($SyncClass.SyncHash.SuperPeerList,$SyncClass.SyncHash.SuperPeerListLock) 29 | [void][System.Windows.Data.BindingOperations]::SetBinding($formMainWindowControlSuperPeerListBox,[System.Windows.Controls.ListBox]::ItemsSourceProperty, $SuperPeerListBinding) 30 | 31 | # Package/Application List 32 | $SyncClass.SyncHash.AppPkgList = New-Object System.Collections.ObjectModel.ObservableCollection[Object] 33 | $SyncClass.SyncHash.AppPkgListLock = New-Object PSObject 34 | $SyncClass.SyncHash.AppPkgView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($SyncClass.SyncHash.AppPkgList) 35 | $formMainWindowControlPackageListBox.DisplayMemberPath = 'Name' 36 | $formMainWindowControlPackageListBox.ItemsSource = $SyncClass.SyncHash.AppPkgView 37 | 38 | # Create a binding to pair the listbox to the observable collection 39 | $AppPkgListBinding = New-Object System.Windows.Data.Binding 40 | $AppPkgListBinding.Source = $SyncClass.SyncHash.AppPkgList 41 | $AppPkgListBinding.Mode = [System.Windows.Data.BindingMode]::OneWay 42 | [void][System.Windows.Data.BindingOperations]::EnableCollectionSynchronization($SyncClass.SyncHash.AppPkgList,$SyncClass.SyncHash.AppPkgListLock) 43 | [void][System.Windows.Data.BindingOperations]::SetBinding($formMainWindowControlPackageListBox,[System.Windows.Controls.ListBox]::ItemsSourceProperty, $AppPkgListBinding) 44 | 45 | # Start running the following tasks in the background 46 | # as they may take a bit of time to run 47 | Start-BackgroundScriptBlock -scriptBlock { 48 | if(!$SyncClass.SyncHash.DBAToolsInstalled) 49 | { 50 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Checking for DBATools...") 51 | # Install DBATools if it doesn't exist 52 | if(!(Get-Module -ListAvailable -Name dbatools)) 53 | { 54 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Installing DBATools for $ENV:Username...") 55 | if(!(Get-PackageProvider -Name NuGet -ListAvailable)) 56 | { 57 | [System.Windows.MessageBox]::Show("DBATools is not installed, and requires NuGet Package Manager to automatically install for the user. Please run the following command from an elevated command prompt:`n`nInstall-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force","ERROR!!",0,48) 58 | $SyncClass.CloseWindow("formMainWindow") 59 | } 60 | Install-Module dbatools -Scope CurrentUser -Force 61 | } 62 | Import-Module dbatools 63 | $SyncClass.SyncHash.DBAToolsInstalled = $true 64 | } 65 | 66 | # Begin Automatic Loading of Content IF Server is set 67 | if(![string]::IsNullOrEmpty($SyncClass.SyncHash.ConfigMgrDS)) 68 | { 69 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Loading SuperPeers") 70 | try{ 71 | $spCommand = @{ 72 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 73 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 74 | } 75 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 76 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 77 | 78 | # Query to get all machines that are super peers 79 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT SP.ResourceID,CS.Domain00,CS.Name00 FROM SuperPeers as SP ` 80 | INNER JOIN Computer_System_DATA as CS on CS.MachineID = SP.ResourceID ` 81 | ORDER BY CS.Name00" 82 | 83 | $SuperPeers = (Connect-DbaInstance @spCommand).Query($Query) 84 | 85 | # Add SuperPeers to the observable collection 86 | foreach($sp in $SuperPeers) 87 | { 88 | $obj = [PSCustomObject]@{ 89 | Name00 = $sp.Name00 90 | Domain00 = $sp.Domain00 91 | ResourceID = $sp.ResourceID 92 | } 93 | $SyncClass.SyncHash.SuperPeerList.add($obj) 94 | } 95 | 96 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Loading Packages") 97 | $spCommand = @{ 98 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 99 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 100 | } 101 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 102 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 103 | 104 | # Query all packages 105 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT DISTINCT SP.ContentID,PL.Name FROM SuperPeerContentMap as SP ` 106 | INNER JOIN SMSPackages_All as PL on SP.ContentID = PL.PkgID ` 107 | ORDER BY PL.Name" 108 | $Packages = @() 109 | $Packages += (Connect-DbaInstance @spCommand).Query($Query) 110 | 111 | 112 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Loading Applications") 113 | $spCommand = @{ 114 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 115 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 116 | } 117 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 118 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 119 | 120 | # Query all applications 121 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT DISTINCT SP.ContentID,LAC.DisplayName as Name FROM SuperPeerContentMap as SP ` 122 | INNER JOIN vSMS_CIToContent as CI on SP.ContentID = CI.Content_UniqueID ` 123 | INNER JOIN fn_ListDeploymentTypeCIs(1033) as LCI on LCI.CI_ID = CI.CI_ID ` 124 | INNER JOIN fn_ListApplicationCIs(1033) as LAC on LAC.ModelName = LCI.AppModelName ` 125 | ORDER BY LAC.DisplayName" 126 | $Apps = @() 127 | $Apps += (Connect-DbaInstance @spCommand).Query($Query) 128 | 129 | # Add apps to observable collection 130 | foreach($p in $Apps) 131 | { 132 | $obj = [PSCustomObject]@{ 133 | ContentID = $p.ContentID 134 | Name = "APP - $($p.Name)" 135 | } 136 | $SyncClass.SyncHash.AppPkgList.add($obj) 137 | } 138 | 139 | # Add packages to observable collection 140 | foreach($p in $Packages) 141 | { 142 | $obj = [PSCustomObject]@{ 143 | ContentID = $p.ContentID 144 | Name = "PKG - $($p.Name)" 145 | } 146 | $SyncClass.SyncHash.AppPkgList.add($obj) 147 | } 148 | } 149 | catch 150 | { 151 | # Something went wrong - probably bad credentials 152 | [System.Windows.Messagebox]::Show("There was an error getting info from the database. Does the account have the appropriate read permissions to the ConfigMgr Database?","ERROR In DB Query") 153 | } 154 | } 155 | $SyncClass.UpdateElement("formMainWindowControlLoadingOverlay","Visibility","Collapsed") 156 | $SyncClass.UpdateElement("formMainWindowControlRefreshButton","IsEnabled",$true) 157 | 158 | # Allow selection changes to refresh the datagrid 159 | $SyncClass.SyncHash.ActOnAny = $true 160 | } 161 | } 162 | 163 | # Run when the window is displayed 164 | $formMainWindow.Add_ContentRendered({ 165 | Invoke-Command -ScriptBlock $RefreshSB 166 | }) 167 | 168 | # Run when the refresh button is pressed 169 | $formMainWindowControlRefreshButton.Add_Click({ 170 | Invoke-Command -ScriptBlock $RefreshSB 171 | }) -------------------------------------------------------------------------------- /Scripts/POWmi/License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Scripts/POWmi/POWmi.psd1: -------------------------------------------------------------------------------- 1 | <# 2 | =========================================================================== 3 | Created with: SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.154 4 | Created on: 9/18/2018 3:18 PM 5 | Created by: 949237a 6 | Organization: 7 | Filename: POWmi.psd1 8 | ------------------------------------------------------------------------- 9 | Module Manifest 10 | ------------------------------------------------------------------------- 11 | Module Name: POWmi 12 | =========================================================================== 13 | #> 14 | 15 | 16 | @{ 17 | 18 | # Script module or binary module file associated with this manifest 19 | ModuleToProcess = 'POWmi.psm1' 20 | 21 | # Version number of this module. 22 | ModuleVersion = '0.1.0.2' 23 | 24 | # ID used to uniquely identify this module 25 | GUID = 'eca2583a-643e-4764-b120-2118b850118d' 26 | 27 | # Author of this module 28 | Author = '949237a' 29 | 30 | # Company or vendor of this module 31 | CompanyName = '' 32 | 33 | # Copyright statement for this module 34 | Copyright = '(c) 2018. All rights reserved.' 35 | 36 | # Description of the functionality provided by this module 37 | Description = 'Module description' 38 | 39 | # Minimum version of the Windows PowerShell engine required by this module 40 | PowerShellVersion = '2.0' 41 | 42 | # Name of the Windows PowerShell host required by this module 43 | PowerShellHostName = '' 44 | 45 | # Minimum version of the Windows PowerShell host required by this module 46 | PowerShellHostVersion = '' 47 | 48 | # Minimum version of the .NET Framework required by this module 49 | DotNetFrameworkVersion = '2.0' 50 | 51 | # Minimum version of the common language runtime (CLR) required by this module 52 | CLRVersion = '2.0.50727' 53 | 54 | # Processor architecture (None, X86, Amd64, IA64) required by this module 55 | ProcessorArchitecture = 'None' 56 | 57 | # Modules that must be imported into the global environment prior to importing 58 | # this module 59 | RequiredModules = @() 60 | 61 | # Assemblies that must be loaded prior to importing this module 62 | RequiredAssemblies = @() 63 | 64 | # Script files (.ps1) that are run in the caller's environment prior to 65 | # importing this module 66 | ScriptsToProcess = @() 67 | 68 | # Type files (.ps1xml) to be loaded when importing this module 69 | TypesToProcess = @() 70 | 71 | # Format files (.ps1xml) to be loaded when importing this module 72 | FormatsToProcess = @() 73 | 74 | # Modules to import as nested modules of the module specified in 75 | # ModuleToProcess 76 | NestedModules = @() 77 | 78 | # Functions to export from this module 79 | FunctionsToExport = @() #For performance, list functions explicitly 80 | 81 | # Cmdlets to export from this module 82 | CmdletsToExport = '' 83 | 84 | # Variables to export from this module 85 | VariablesToExport = '*' 86 | 87 | # Aliases to export from this module 88 | AliasesToExport = '' #For performance, list alias explicitly 89 | 90 | # DSC class resources to export from this module. 91 | #DSCResourcesToExport = '' 92 | 93 | # List of all modules packaged with this module 94 | ModuleList = @() 95 | 96 | # List of all files packaged with this module 97 | FileList = @() 98 | 99 | # Private data to pass to the module specified in ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 100 | PrivateData = @{ 101 | 102 | #Support for PowerShellGet galleries. 103 | PSData = @{ 104 | 105 | # Tags applied to this module. These help with module discovery in online galleries. 106 | # Tags = @() 107 | 108 | # A URL to the license for this module. 109 | # LicenseUri = '' 110 | 111 | # A URL to the main website for this project. 112 | # ProjectUri = '' 113 | 114 | # A URL to an icon representing this module. 115 | # IconUri = '' 116 | 117 | # ReleaseNotes of this module 118 | # ReleaseNotes = '' 119 | 120 | } # End of PSData hashtable 121 | 122 | } # End of PrivateData hashtable 123 | } 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /Scripts/POWmi/POWmi.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | =========================================================================== 3 | Created with: SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.154 4 | Created on: 9/18/2018 3:18 PM 5 | Created by: 949237a 6 | Organization: 7 | Filename: POWmi.psm1 8 | ------------------------------------------------------------------------- 9 | Module Name: POWmi 10 | =========================================================================== 11 | #> 12 | 13 | $scriptPath = Split-Path $MyInvocation.MyCommand.Path 14 | #region Load Private Functions 15 | try 16 | { 17 | Get-ChildItem "$scriptPath\Private" -filter *.ps1 | Select-Object -ExpandProperty FullName | ForEach-Object{ 18 | . $_ 19 | } 20 | } 21 | catch 22 | { 23 | Write-Warning "There was an error loading $($function) and the error is $($psitem.tostring())" 24 | exit 25 | } 26 | 27 | #region Load Public Functions 28 | try 29 | { 30 | Get-ChildItem "$scriptPath\Public" -filter *.ps1 | Select-Object -ExpandProperty FullName | ForEach-Object{ 31 | . $_ 32 | } 33 | } 34 | catch 35 | { 36 | Write-Warning "There was an error loading $($function) and the error is $($psitem.tostring())" 37 | exit 38 | } 39 | -------------------------------------------------------------------------------- /Scripts/POWmi/Private/ConvertFrom-Base64StringToObject.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-Base64ToObject 2 | { 3 | [CmdletBinding()] 4 | param 5 | ( 6 | [Parameter(Mandatory = $true, 7 | Position = 0)] 8 | [ValidateNotNullOrEmpty()] 9 | [Alias('string')] 10 | [string]$inputString 11 | ) 12 | $data = [System.convert]::FromBase64String($inputString) 13 | $memoryStream = New-Object System.Io.MemoryStream 14 | $memoryStream.write($data, 0, $data.length) 15 | $memoryStream.seek(0, 0) | Out-Null 16 | $streamReader = New-Object System.IO.StreamReader(New-Object System.IO.Compression.GZipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress)) 17 | $decompressedData = ConvertFrom-CliXml ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($($streamReader.readtoend())))) 18 | return $decompressedData 19 | } -------------------------------------------------------------------------------- /Scripts/POWmi/Private/ConvertFrom-CLIXml.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-CliXml 2 | { 3 | param ( 4 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] 5 | [ValidateNotNullOrEmpty()] 6 | [String[]]$InputObject 7 | ) 8 | begin 9 | { 10 | $OFS = "`n" 11 | [String]$xmlString = "" 12 | } 13 | process 14 | { 15 | $xmlString += $InputObject 16 | } 17 | end 18 | { 19 | $type = [PSObject].Assembly.GetType('System.Management.Automation.Deserializer') 20 | $ctor = $type.GetConstructor('instance,nonpublic', $null, @([xml.xmlreader]), $null) 21 | $sr = New-Object System.IO.StringReader $xmlString 22 | $xr = New-Object System.Xml.XmlTextReader $sr 23 | $deserializer = $ctor.Invoke($xr) 24 | $done = $type.GetMethod('Done', [System.Reflection.BindingFlags]'nonpublic,instance') 25 | while (!$type.InvokeMember("Done", "InvokeMethod,NonPublic,Instance", $null, $deserializer, @())) 26 | { 27 | try 28 | { 29 | $type.InvokeMember("Deserialize", "InvokeMethod,NonPublic,Instance", $null, $deserializer, @()) 30 | } 31 | catch 32 | { 33 | Write-Warning "Could not deserialize ${string}: $_" 34 | } 35 | } 36 | $xr.Close() 37 | $sr.Dispose() 38 | } 39 | } -------------------------------------------------------------------------------- /Scripts/POWmi/Private/ConvertTo-Base64StringFromObject.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-Base64StringFromObject 2 | { 3 | [CmdletBinding()] 4 | [OutputType([string])] 5 | param 6 | ( 7 | [Parameter(Mandatory = $true, 8 | Position = 0)] 9 | [ValidateNotNullOrEmpty()] 10 | [Alias('object', 'data','input')] 11 | [psobject]$inputObject 12 | ) 13 | $tempString = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([management.automation.psserializer]::Serialize($inputObject))) 14 | $memoryStream = New-Object System.IO.MemoryStream 15 | $compressionStream = New-Object System.IO.Compression.GZipStream($memoryStream, [System.io.compression.compressionmode]::Compress) 16 | $streamWriter = New-Object System.IO.streamwriter($compressionStream) 17 | $streamWriter.write($tempString) 18 | $streamWriter.close() 19 | $compressedData = [System.convert]::ToBase64String($memoryStream.ToArray()) 20 | return $compressedData 21 | } -------------------------------------------------------------------------------- /Scripts/POWmi/Private/ConvertTo-CliXML.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-CliXml 2 | { 3 | param ( 4 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] 5 | [ValidateNotNullOrEmpty()] 6 | [PSObject[]]$InputObject 7 | ) 8 | return [management.automation.psserializer]::Serialize($InputObject) 9 | } -------------------------------------------------------------------------------- /Scripts/POWmi/Public/Invoke-POWmi.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-POWmi 2 | { 3 | [CmdletBinding(DefaultParameterSetName = 'Credential')] 4 | param 5 | ( 6 | [ValidateNotNullOrEmpty()] 7 | [Alias('Name')] 8 | $PipeName = ([guid]::NewGuid()).Guid.ToString(), 9 | [Parameter(Mandatory = $true)] 10 | [ValidateNotNullOrEmpty()] 11 | [scriptblock]$ScriptBlock, 12 | [Parameter(Mandatory = $false)] 13 | [ValidateNotNullOrEmpty()] 14 | [string]$ComputerName = 'localhost', 15 | [Parameter(ParameterSetName = 'Credential', 16 | Mandatory = $true)] 17 | [ValidateNotNullOrEmpty()] 18 | [pscredential]$Credential, 19 | [ValidateRange(1000, 900000)] 20 | [int32]$Timeout = 120000, 21 | [Parameter(ParameterSetName = 'ByPassCreds')] 22 | [switch]$BypassCreds 23 | ) 24 | 25 | $scriptBlockPreEncoded = [scriptblock]{ 26 | #region support functions 27 | function ConvertTo-CliXml 28 | { 29 | param ( 30 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] 31 | [ValidateNotNullOrEmpty()] 32 | [PSObject[]]$InputObject 33 | ) 34 | return [management.automation.psserializer]::Serialize($InputObject) 35 | } 36 | 37 | function ConvertTo-Base64StringFromObject 38 | { 39 | [CmdletBinding()] 40 | param 41 | ( 42 | [Parameter(Mandatory = $true, 43 | ValueFromPipeline = $true, 44 | Position = 0)] 45 | [ValidateNotNullOrEmpty()] 46 | [object]$inputobject 47 | ) 48 | 49 | $holdingXml = ConvertTo-CliXml -InputObject $inputobject 50 | $preConversion_bytes = [System.Text.Encoding]::UTF8.GetBytes($holdingXml) 51 | $preconversion_64 = [System.Convert]::ToBase64String($preConversion_bytes) 52 | $memoryStream = New-Object System.IO.MemoryStream 53 | $compressionStream = New-Object System.IO.Compression.GZipStream($memoryStream, [System.io.compression.compressionmode]::Compress) 54 | $streamWriter = New-Object System.IO.streamwriter($compressionStream) 55 | $streamWriter.write($preconversion_64) 56 | $streamWriter.close() 57 | $compressedData = [System.convert]::ToBase64String($memoryStream.ToArray()) 58 | return $compressedData 59 | } 60 | #endregion 61 | 62 | $namedPipe = new-object System.IO.Pipes.NamedPipeServerStream "", "Out" 63 | $namedPipe.WaitForConnection() 64 | $streamWriter = New-Object System.IO.StreamWriter $namedPipe 65 | $streamWriter.AutoFlush = $true 66 | $TempResultPreConversion = &{ } 67 | $results = ConvertTo-Base64StringFromObject -inputObject $TempResultPreConversion 68 | $streamWriter.WriteLine("$($results)") 69 | $streamWriter.dispose() 70 | $namedPipe.dispose() 71 | 72 | } 73 | 74 | $scriptBlockPreEncoded = $scriptBlockPreEncoded -replace "", $PipeName 75 | $scriptBlockPreEncoded = $scriptBlockPreEncoded -replace "", $ScriptBlock 76 | $byteCommand = [System.Text.encoding]::UTF8.GetBytes($scriptBlockPreEncoded) 77 | $encodedScriptBlock = [convert]::ToBase64string($byteCommand) 78 | 79 | if ($($env:computername) -eq $ComputerName -or $BypassCreds) 80 | { 81 | $holderData = Invoke-wmimethod -computername "$($ComputerName)" -class win32_process -name create -argumentlist "powershell.exe (invoke-command ([scriptblock]::Create([system.text.encoding]::UTF8.GetString([System.convert]::FromBase64string('$($encodedScriptBlock)')))))" 82 | } 83 | else 84 | { 85 | $holderData = Invoke-wmimethod -computername "$($ComputerName)" -class win32_process -name create -argumentlist "powershell.exe (invoke-command ([scriptblock]::Create([system.text.encoding]::UTF8.GetString([System.convert]::FromBase64string(`"$($encodedScriptBlock))`"))))" -Credential $Credential 86 | } 87 | 88 | $namedPipe = New-Object System.IO.Pipes.NamedPipeClientStream $ComputerName, "$($PipeName)", "In" 89 | $namedPipe.connect($timeout) 90 | $streamReader = New-Object System.IO.StreamReader $namedPipe 91 | 92 | while ($null -ne ($data = $streamReader.ReadLine())) 93 | { 94 | $tempData = $data 95 | } 96 | 97 | $streamReader.dispose() 98 | $namedPipe.dispose() 99 | 100 | ConvertFrom-Base64ToObject -inputString $tempData 101 | } 102 | #https://github.com/threatexpress/invoke-pipeshell/blob/master/Invoke-PipeShell.ps1 -------------------------------------------------------------------------------- /Scripts/WiringAboutDialog.ps1: -------------------------------------------------------------------------------- 1 | # Create a custom dialog window and add it to the main window 2 | $AboutDialog = [MahApps.Metro.Controls.Dialogs.CustomDialog]::new($formMainWindow) 3 | $AboutDialog.AddChild($formAboutDialog) 4 | 5 | # Show the dialog when about is pressed 6 | $formMainWindowControlAboutButton.Add_Click({ 7 | $settings = [MahApps.Metro.Controls.Dialogs.MetroDialogSettings]::new() 8 | $settings.ColorScheme = [MahApps.Metro.Controls.Dialogs.MetroDialogColorScheme]::Theme 9 | [MahApps.Metro.Controls.Dialogs.DialogManager]::ShowMetroDialogAsync($formMainWindow, $AboutDialog, $settings) 10 | }) 11 | 12 | # Close the dialog when ok button pressed 13 | $formAboutDialogControlAboutOKButton.Add_Click({ 14 | $AboutDialog.RequestCloseAsync() 15 | }) -------------------------------------------------------------------------------- /Scripts/WiringMainWindow.ps1: -------------------------------------------------------------------------------- 1 | # Create the datagrid observable collection (for filtering) 2 | $SyncClass.SyncHash.DataGridList = New-Object System.Collections.ObjectModel.ObservableCollection[Object] 3 | $SyncClass.SyncHash.DataGridListLock = New-Object PSObject 4 | $SyncClass.SyncHash.DataGridView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($SyncClass.SyncHash.DataGridList) 5 | $formMainWindowControlContentDataGrid.ItemsSource = $SyncClass.SyncHash.DataGridView 6 | 7 | # Create a binding for the datagrid to the observable collection 8 | $DataGridListBinding = New-Object System.Windows.Data.Binding 9 | $DataGridListBinding.Source = $SyncClass.SyncHash.DataGridList 10 | $DataGridListBinding.Mode = [System.Windows.Data.BindingMode]::OneWay 11 | [void][System.Windows.Data.BindingOperations]::EnableCollectionSynchronization($SyncClass.SyncHash.DataGridList,$SyncClass.SyncHash.DataGridListLock) 12 | [void][System.Windows.Data.BindingOperations]::SetBinding($formMainWindowControlContentDataGrid,[System.Windows.Controls.ListBox]::ItemsSourceProperty, $DataGridListBinding) 13 | 14 | # Allow action when package or superpeer selected 15 | $SyncClass.SyncHash.ActOnPackage = $true 16 | $SyncClass.SyncHash.ActOnSuperPeer = $true 17 | 18 | # Filtering when text is changed 19 | $formMainWindowControlSuperPeerSearch.Add_TextChanged({ 20 | $SyncClass.SyncHash.SuperPeerView.Filter = {$args[0].Name00 -match $this.Text} 21 | }) 22 | 23 | # Filtering when text is changed 24 | $formMainWindowControlPackageSearch.Add_TextChanged({ 25 | $SyncClass.SyncHash.AppPkgView.Filter = {$args[0].Name -match $this.Text} 26 | }) 27 | 28 | # Filtering when text is changed 29 | $formMainWindowControlDataGridFilter.Add_TextChanged({ 30 | $SyncClass.SyncHash.DataGridView.Filter = {$args[0] -match $this.Text} 31 | }) 32 | 33 | # When a selection is made for a SuperPeer 34 | $formMainWindowControlSuperPeerListBox.Add_SelectionChanged({ 35 | # Make sure we want to act - so that we don't do double queries and confuse the app... threading is fun :) 36 | if($SyncClass.SyncHash.ActOnSuperPeer -and $SyncClass.SyncHash.ActOnAny) 37 | { 38 | # Keep user from making changes accidentally 39 | $formMainWindowControlLoadingOverlay.Visibility = "Visible" 40 | $formMainWindowControlRefreshButton.IsEnabled = $false 41 | 42 | # Deselect any selected package without acting on it 43 | $SyncClass.SyncHash.ActOnPackage = $false 44 | $formMainWindowControlPackageListBox.SelectedIndex = -1 45 | $SyncClass.SyncHash.ActOnPackage = $true 46 | 47 | # Clear the datagrid 48 | $SyncClass.SyncHash.DataGridList.Clear() 49 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Loading Applications/Packages on SuperPeer") 50 | 51 | # Sync the selected item 52 | $SyncClass.SyncHash.SelectedItem = $formMainWindowControlSuperPeerListBox.SelectedItem 53 | 54 | # Let's do this in the background because it will take some time 55 | Start-BackgroundScriptBlock -scriptBlock { 56 | try{ 57 | $spCommand = @{ 58 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 59 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 60 | } 61 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 62 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 63 | 64 | #Get applications for selected system 65 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT SP.ResourceID,CS.Domain00,CS.Name00,SP.ContentID,SP.Version FROM SuperPeerContentMap as SP ` 66 | INNER JOIN vSMS_CIToContent as CI on SP.ContentID = CI.Content_UniqueID ` 67 | INNER JOIN fn_ListDeploymentTypeCIs(1033) as LCI on LCI.CI_ID = CI.CI_ID ` 68 | INNER JOIN fn_ListApplicationCIs(1033) as LAC on LAC.ModelName = LCI.AppModelName ` 69 | INNER JOIN Computer_System_DATA as CS on CS.MachineID = SP.ResourceID ` 70 | WHERE ResourceID = $($SyncClass.SyncHash.SelectedItem.ResourceID)" 71 | $Applications = @() 72 | $Applications += (Connect-DbaInstance @spCommand).Query($Query) 73 | 74 | #Add applications to the grid 75 | foreach($a in $Applications) 76 | { 77 | $obj = [PSCustomObject]@{ 78 | Name = $a.Name00 79 | Domain = $a.Domain00 80 | ResourceID = $a.ResourceID 81 | ContentID = $a.ContentID 82 | Version = $a.Version 83 | } 84 | $SyncClass.SyncHash.DataGridList.add($obj) 85 | } 86 | 87 | $spCommand = @{ 88 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 89 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 90 | } 91 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 92 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 93 | 94 | #Get packages for selected system 95 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT SP.ResourceID,CS.Name00,CS.Domain00,SP.ContentID,SP.Version FROM SuperPeerContentMap as SP ` 96 | INNER JOIN SMSPackages_All as PL on SP.ContentID = PL.PkgID ` 97 | INNER JOIN Computer_System_DATA as CS on CS.MachineID = SP.ResourceID ` 98 | WHERE SP.ResourceID = $($SyncClass.SyncHash.SelectedItem.ResourceID)" 99 | $Packages = @() 100 | $Packages += (Connect-DbaInstance @spCommand).Query($Query) 101 | 102 | #Add packages to the grid 103 | foreach($p in $Packages) 104 | { 105 | $obj = [PSCustomObject]@{ 106 | Name = $p.Name00 107 | Domain = $p.Domain00 108 | ResourceID = $p.ResourceID 109 | ContentID = $p.ContentID 110 | Version = $p.Version 111 | } 112 | $SyncClass.SyncHash.DataGridList.add($obj) 113 | } 114 | 115 | #Clear the filter 116 | $SyncClass.UpdateElement("formMainWindowControlDataGridFilter","Text","") 117 | } 118 | catch 119 | { 120 | [System.Windows.Messagebox]::Show("There was an error getting info from the database. Does the account have the appropriate read permissions to the ConfigMgr Database?","ERROR In DB Query") 121 | } 122 | 123 | # Allow the user to click around again 124 | $SyncClass.UpdateElement("formMainWindowControlLoadingOverlay","Visibility","Collapsed") 125 | $SyncClass.UpdateElement("formMainWindowControlRefreshButton","IsEnabled",$true) 126 | } 127 | } 128 | }) 129 | 130 | # When a selection is made for a Package/App 131 | $formMainWindowControlPackageListBox.Add_SelectionChanged({ 132 | #Only act if we should 133 | if($SyncClass.SyncHash.ActOnPackage -and $SyncClass.SyncHash.ActOnAny) 134 | { 135 | # User NO TOUCHY 136 | $formMainWindowControlLoadingOverlay.Visibility = "Visible" 137 | $formMainWindowControlRefreshButton.IsEnabled = $false 138 | 139 | # Clear any selections on the super peer 140 | $SyncClass.SyncHash.ActOnSuperPeer = $false 141 | $formMainWindowControlSuperPeerListBox.SelectedIndex = -1 142 | $SyncClass.SyncHash.ActOnSuperPeer = $true 143 | 144 | # Clear the datagrid 145 | $SyncClass.SyncHash.DataGridList.Clear() 146 | $SyncClass.UpdateElement("formMainWindowControlLoadingText","Content","Loading SuperPeers with Application/Package") 147 | 148 | # Share the selected item with background threads 149 | $SyncClass.SyncHash.SelectedItem = $formMainWindowControlPackageListBox.SelectedItem 150 | 151 | # Let's do this in the background 152 | Start-BackgroundScriptBlock -scriptBlock { 153 | $sqlServer = $SyncClass.SyncHash.ConfigMgrDS 154 | $database = $SyncClass.SyncHash.ConfigMgrSC 155 | 156 | try{ 157 | # If an application was selected 158 | if($($SyncClass.SyncHash.SelectedItem.ContentID) -like "Content_*") 159 | { 160 | 161 | $spCommand = @{ 162 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 163 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 164 | } 165 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 166 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 167 | 168 | # Query to get devices used by application 169 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT SP.ResourceID,CS.Name00,CS.Domain00,SP.ContentID,SP.Version FROM SuperPeerContentMap as SP ` 170 | INNER JOIN vSMS_CIToContent as CI on SP.ContentID = CI.Content_UniqueID ` 171 | INNER JOIN fn_ListDeploymentTypeCIs(1033) as LCI on LCI.CI_ID = CI.CI_ID ` 172 | INNER JOIN fn_ListApplicationCIs(1033) as LAC on LAC.ModelName = LCI.AppModelName ` 173 | INNER JOIN Computer_System_DATA as CS on CS.MachineID = SP.ResourceID ` 174 | WHERE SP.ContentID = '$($SyncClass.SyncHash.SelectedItem.ContentID)' ` 175 | ORDER BY CS.Name00" 176 | $Applications = @() 177 | $Applications += (Connect-DbaInstance @spCommand).Query($Query) 178 | 179 | # Add devices to the grid that have the application 180 | foreach($a in $Applications) 181 | { 182 | $obj = [PSCustomObject]@{ 183 | ResourceID = $a.ResourceID 184 | Name = $a.Name00 185 | Domain = $a.Domain00 186 | ContentID = $a.ContentID 187 | Version = $a.Version 188 | } 189 | $SyncClass.SyncHash.DataGridList.add($obj) 190 | } 191 | } 192 | else # Package was selected 193 | { 194 | $spCommand = @{ 195 | 'SqlInstance' = $SyncClass.SyncHash.ConfigMgrDS 196 | 'Database' = "CM_$($SyncClass.SyncHash.ConfigMgrSC)" 197 | } 198 | if($SyncClass.SyncHash.AlternateCredentials){$spCommand['SqlCredential'] = $SyncClass.SyncHash.AlternateCredentials} 199 | elseif($spCommand['SqlCredential']){ $spCommand.Remove('SqlCredential') } 200 | 201 | # Get the devices that have the package 202 | $Query = "USE CM_$($SyncClass.SyncHash.ConfigMgrSC); SELECT SP.ResourceID,CS.Name00,CS.Domain00,SP.ContentID,SP.Version FROM SuperPeerContentMap as SP ` 203 | INNER JOIN SMSPackages_All as PL on SP.ContentID = PL.PkgID ` 204 | INNER JOIN Computer_System_DATA as CS on CS.MachineID = SP.ResourceID ` 205 | WHERE SP.ContentID = '$($SyncClass.SyncHash.SelectedItem.ContentID)' ` 206 | ORDER BY CS.Name00" 207 | $Packages = @() 208 | $Packages += (Connect-DbaInstance @spCommand).Query($Query) 209 | 210 | # Add devices to the list that have the package 211 | foreach($p in $Packages) 212 | { 213 | $obj = [PSCustomObject]@{ 214 | ResourceID = $p.ResourceID 215 | Name = $p.Name00 216 | Domain = $p.Domain00 217 | ContentID = $p.ContentID 218 | Version = $p.Version 219 | } 220 | $SyncClass.SyncHash.DataGridList.add($obj) 221 | } 222 | } 223 | 224 | #Clear the filter 225 | $SyncClass.UpdateElement("formMainWindowControlDataGridFilter","Text","") 226 | } 227 | catch 228 | { 229 | [System.Windows.Messagebox]::Show("There was an error getting info from the database. Does the account have the appropriate read permissions to the ConfigMgr Database?","ERROR In DB Query") 230 | } 231 | 232 | # Give the user access again 233 | $SyncClass.UpdateElement("formMainWindowControlLoadingOverlay","Visibility","Collapsed") 234 | $SyncClass.UpdateElement("formMainWindowControlRefreshButton","IsEnabled",$true) 235 | } 236 | } 237 | }) 238 | 239 | # If there are selected items, enable the buttons 240 | $formMainWindowControlContentDataGrid.Add_SelectionChanged({ 241 | if($formMainWindowControlContentDataGrid.SelectedItems) 242 | { 243 | #$formMainWindowControlValidateContent.IsEnabled = $true 244 | $formMainWindowControlRemoveContent.IsEnabled = $true 245 | } 246 | else 247 | { 248 | #$formMainWindowControlValidateContent.IsEnabled = $false 249 | $formMainWindowControlRemoveContent.IsEnabled = $false 250 | } 251 | }) 252 | 253 | <# 254 | $formMainWindowControlValidateContent.Add_Click({ 255 | 256 | foreach($si in $formMainWindowControlContentDataGrid.SelectedItems) 257 | { 258 | $gwmi = Get-WmiObject -ComputerName "$($s.Name)" -Namespace "root\ccm\SoftMgmtAgent" -Query "select * from CacheInfoEx where ContentId = '$($si.ContentID)' and ContentVer = '$($si.Version)'" 259 | Write-Host $gwmi.Location 260 | $computer = "$($si.Name).$($si.Domain)" 261 | Invoke-POWmi -ScriptBlock {Get-FileHash C:\Windows} -ComputerName $computer -BypassCreds 262 | } 263 | }) 264 | #> 265 | 266 | # What hapens when you click to remove content?? All this fun 267 | $formMainWindowControlRemoveContent.Add_Click({ 268 | $SyncClass.SyncHash.GridSelectedItems = @() 269 | foreach($si in $formMainWindowControlContentDataGrid.SelectedItems) 270 | { 271 | $SyncClass.SyncHash.GridSelectedItems += [pscustomobject]@{ 272 | ContentId = $si.ContentId; 273 | Version = $si.Version; 274 | Name = $si.Name; 275 | Domain = $si.Domain; 276 | } 277 | } 278 | 279 | $formMainWindowControlLoadingText.Content = "Removing Selected Content" 280 | $formMainWindowControlLoadingOverlay.Visibility = "Visible" 281 | $formMainWindowControlRefreshButton.IsEnabled = $false 282 | 283 | # Do it in the background :D 284 | Start-BackgroundScriptBlock -scriptBlock { 285 | Import-Module "$($SyncClass.SyncHash.ScriptRoot)\Scripts\POWmi\POWmi.psm1" 286 | foreach($si in $SyncClass.SyncHash.GridSelectedItems) 287 | { 288 | # Create a script to run over WMI 289 | $sb = [ScriptBlock]::Create(" 290 | `$rmObj = New-Object -ComObject 'UIResource.UIResourceMgr' 291 | `$cacheObject = `$rmObj.GetCacheInfo() 292 | `$cacheItem = `$cacheObject.GetCacheElements() | Where-Object -Property ContentId -EQ -Value `"$($si.ContentId)`" | Where-Object -Property ContentVersion -EQ -Value `"$($si.Version)`" 293 | `$cacheObject.DeleteCacheElementEx(`$cacheItem.CacheElementId,`$true) 294 | ") 295 | $computer = "$($si.Name).$($si.Domain)" 296 | 297 | # Execute the script over WMI 298 | $null = Invoke-POWmi -ComputerName $computer -ScriptBlock $sb -BypassCreds -ErrorAction SilentlyContinue 299 | } 300 | $SyncClass.UpdateElement("formMainWindowControlLoadingOverlay","Visibility","Collapsed") 301 | $SyncClass.UpdateElement("formMainWindowControlRefreshButton","IsEnabled",$true) 302 | } 303 | 304 | # Remove all selected items from list (hacky, but it works) 305 | while($formMainWindowControlContentDataGrid.SelectedItems) 306 | { 307 | $formMainWindowControlContentDataGrid.ItemsSource.Remove($formMainWindowControlContentDataGrid.SelectedItems[0]) 308 | } 309 | }) -------------------------------------------------------------------------------- /Scripts/WiringSettingsDialog.ps1: -------------------------------------------------------------------------------- 1 | # Create a custom dialog window and add it to the main window 2 | $SettingsDialog = [MahApps.Metro.Controls.Dialogs.CustomDialog]::new($formMainWindow) 3 | $SettingsDialog.AddChild($formSettingsDialog) 4 | 5 | # Show the dialog when settings is pressed 6 | $formMainWindowControlSettingsButton.Add_Click({ 7 | $settings = [MahApps.Metro.Controls.Dialogs.MetroDialogSettings]::new() 8 | $settings.ColorScheme = [MahApps.Metro.Controls.Dialogs.MetroDialogColorScheme]::Theme 9 | [MahApps.Metro.Controls.Dialogs.DialogManager]::ShowMetroDialogAsync($formMainWindow, $SettingsDialog, $settings) 10 | }) 11 | 12 | # Close the dialog when cancel button pressed 13 | $formSettingsDialogControlSettingsCancelButton.Add_Click({ 14 | $SettingsDialog.RequestCloseAsync() 15 | }) 16 | 17 | # Use Alternate Credentials Button 18 | $formSettingsDialogControlSettingsSetCredentials.Add_Click({ 19 | try 20 | { 21 | $SyncClass.SyncHash.AlternateCredentials = (Get-Credential) 22 | $formSettingsDialogControlSettingsCredentialsName.Content = $SyncClass.SyncHash.AlternateCredentials.UserName 23 | } 24 | catch 25 | { 26 | $formSettingsDialogControlSettingsCredentialsName.Content = "(no credentials set)" 27 | $SyncClass.SyncHash.AlternateCredentials = $null 28 | } 29 | }) 30 | 31 | # Save actions 32 | $formSettingsDialogControlSettingsSaveButton.Add_Click({ 33 | #New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name PSDSSame -Value $formSettingsDialogControlSettingsCMDSSame.IsChecked -PropertyType String -Force | Out-Null 34 | #New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrPS -Value $formSettingsDialogControlSettingsCMPS.Text -PropertyType String -Force | Out-Null 35 | New-Item -Path "HKCU:\Software\Z-NERD\PCE\" -Force | Out-Null # Base Reg Key 36 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrDS -Value ($formSettingsDialogControlSettingsCMDS.Text) -PropertyType String -Force | Out-Null # Database server 37 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrSC -Value $formSettingsDialogControlSettingsCMSC.Text -PropertyType String -Force | Out-Null # Site code 38 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UseAlternateCredentials -Value $formSettingsDialogControlSettingsUseCredentials.IsChecked -PropertyType String -Force | Out-Null # Use alternates 39 | # Only store credentials if checkbox is checked and credentials are set 40 | if($SyncClass.SyncHash.AlternateCredentials -and $formSettingsDialogControlSettingsUseCredentials.IsChecked) 41 | { 42 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UserName -Value $SyncClass.SyncHash.AlternateCredentials.UserName -PropertyType String -Force | Out-Null 43 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name SecureString -Value (ConvertFrom-SecureString $SyncClass.SyncHash.AlternateCredentials.Password) -PropertyType String -Force | Out-Null 44 | $SyncClass.SyncHash.AlternateCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SyncClass.SyncHash.AlternateCredentials.UserName,$SyncClass.SyncHash.AlternateCredentials.Password 45 | } 46 | else # otherwise clear the values 47 | { 48 | $formSettingsDialogControlSettingsUseCredentials.IsChecked = $false 49 | $formSettingsDialogControlSettingsCredentialsName.Content = "(no credentials set)" 50 | $SyncClass.SyncHash.AlternateCredentials = $null 51 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UseAlternateCredentials -Value False -PropertyType String -Force | Out-Null 52 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UserName -Value "" -PropertyType String -Force | Out-Null 53 | New-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name SecureString -Value "" -PropertyType String -Force | Out-Null 54 | $SyncClass.SyncHash.AlternateCredentials = $null 55 | } 56 | #$SyncClass.SyncHash.ConfigMgrPS = $formSettingsDialogControlSettingsCMPS.Text #Share primary site with background threads 57 | $SyncClass.SyncHash.ConfigMgrDS = $formSettingsDialogControlSettingsCMDS.Text #Share database server with background threads 58 | # $SyncClass.SyncHash.PSDSSame = $formSettingsDialogControlSettingsCMDSSame.IsChecked #Share "shared database/site server" with background threads 59 | $SyncClass.SyncHash.ConfigMgrSC = $formSettingsDialogControlSettingsCMSC.Text #Share site code with background threads 60 | $SyncClass.SyncHash.UseAlternateCredentials = $formSettingsDialogControlSettingsUseCredentials.IsChecked #Share shared credentials with background thread 61 | 62 | $SettingsDialog.RequestCloseAsync() # Close dialog 63 | }) 64 | 65 | # Window Loaded 66 | $formMainWindow.Add_Loaded({ 67 | # Initialize Variables 68 | $SyncClass.SyncHash.AlternateCredentials = $null 69 | 70 | try 71 | { 72 | #$formSettingsDialogControlSettingsCMPS.Text = (Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrPS -ErrorAction SilentlyContinue).ConfigMgrPS # Get primary server 73 | $formSettingsDialogControlSettingsCMDS.Text = (Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrDS -ErrorAction SilentlyContinue).ConfigMgrDS # Get database server 74 | $formSettingsDialogControlSettingsCMSC.Text = (Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name ConfigMgrSC -ErrorAction SilentlyContinue).ConfigMgrSC # Get site code 75 | 76 | #$formSettingsDialogControlSettingsCMDSSame.IsChecked = [System.Convert]::ToBoolean((Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name PSDSSame -ErrorAction SilentlyContinue).PSDSSame) 77 | #if($formSettingsDialogControlSettingsCMDSSame.IsChecked) 78 | #{ 79 | # $formSettingsDialogControlSettingsCMDS.IsEnabled = $false 80 | #} 81 | 82 | #Convert creds checkbox from registry 83 | $formSettingsDialogControlSettingsUseCredentials.IsChecked = [System.Convert]::ToBoolean((Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UseAlternateCredentials -ErrorAction SilentlyContinue).UseAlternateCredentials) 84 | 85 | #Load the credentials 86 | if($formSettingsDialogControlSettingsUseCredentials.IsChecked) 87 | { 88 | $UN = (Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name UserName -ErrorAction SilentlyContinue).UserName 89 | $SS = (Get-ItemProperty -Path "HKCU:\Software\Z-NERD\PCE\" -Name SecureString -ErrorAction SilentlyContinue).SecureString | ConvertTo-SecureString 90 | $formSettingsDialogControlSettingsCredentialsName.Content = $UN 91 | $SyncClass.SyncHash.AlternateCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UN,$SS 92 | } 93 | #$SyncClass.SyncHash.ConfigMgrPS = $formSettingsDialogControlSettingsCMPS.Text 94 | #$SyncClass.SyncHash.PSDSSame = $formSettingsDialogControlSettingsCMDSSame.IsChecked 95 | 96 | # Load variables to synchash to share with background threads 97 | $SyncClass.SyncHash.ConfigMgrDS = (&{if($formSettingsDialogControlSettingsCMDSSame.IsChecked){$formSettingsDialogControlSettingsCMPS.Text}else{$formSettingsDialogControlSettingsCMDS.Text}}) 98 | $SyncClass.SyncHash.ConfigMgrSC = $formSettingsDialogControlSettingsCMSC.Text 99 | $SyncClass.SyncHash.UseAlternateCredentials = $formSettingsDialogControlSettingsUseCredentials.IsChecked 100 | } 101 | catch 102 | { 103 | Write-Warning "Error loading settings. Possibly first launch?" 104 | } 105 | }) 106 | 107 | # Same DB/PS 108 | #$formSettingsDialogControlSettingsCMDSSame.Add_Checked({ 109 | # $formSettingsDialogControlSettingsCMDS.IsEnabled = $false 110 | # $formSettingsDialogControlSettingsCMDS.Text = $formSettingsDialogControlSettingsCMPS.Text 111 | #}) 112 | 113 | #$formSettingsDialogControlSettingsCMDSSame.Add_Unchecked({ 114 | # $formSettingsDialogControlSettingsCMDS.IsEnabled = $true 115 | #}) 116 | 117 | #$formSettingsDialogControlSettingsCMPS.Add_TextChanged({ 118 | # if($formSettingsDialogControlSettingsCMDSSame.IsChecked) 119 | # { 120 | # $formSettingsDialogControlSettingsCMDS.Text = $formSettingsDialogControlSettingsCMPS.Text 121 | # } 122 | #}) -------------------------------------------------------------------------------- /XAML/AboutDialog.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Peer Cache Explorer 13 | 14 | Copyright (c) 2019 - The Z-Nerd 15 | 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | 35 | 36 | 37 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /XAML/SettingsDialog.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |