├── 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 += "$($xml.FirstChild.Name).Resources>"
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 |
38 |
--------------------------------------------------------------------------------
/XAML/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
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 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
75 |
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 |
42 |
43 |
44 |
--------------------------------------------------------------------------------