├── UI
├── UI.zip
├── .images
│ ├── auth.png
│ ├── actions.png
│ ├── toolView.png
│ ├── multiDeviceView.png
│ └── singleDeviceView.png
├── libaries
│ ├── ControlzEx.dll
│ ├── MahApps.Metro.dll
│ ├── SimpleDialogs.dll
│ ├── IntuneDeviceInventory.ico
│ └── IntuneDeviceInventory.lnk
├── Install-IntuneDeviceInventoryUI.ps1
├── xaml
│ ├── message.xaml
│ ├── authSelection.xaml
│ ├── authServicePrinciple.xaml
│ └── ui.xaml
├── readme.md
├── modules
│ ├── utility.psm1
│ ├── deviceInventoryHandler.psm1
│ ├── authHandler.psm1
│ └── uiHandler.psm1
└── Start-IntuneDeviceInventoryUi.ps1
├── Module
└── IntuneDeviceInventory
│ ├── IntuneDeviceInventory.psd1
│ ├── IntuneDeviceInventory.psm1
│ ├── Public
│ ├── Test-4IDIDevices.ps1
│ ├── Get-noneIDIReference.ps1
│ ├── Add-IDIProperty.ps1
│ ├── Set-IDIDevice.ps1
│ ├── Invoke-PagingRequest.ps1
│ ├── Remove-IDIAppConnection.ps1
│ ├── Set-IDIDeviceNotes.ps1
│ ├── Get-IDIDeviceNotes.ps1
│ ├── Connect-IDI.ps1
│ ├── Save-IDIAppConnection.ps1
│ ├── Backup-IDI.ps1
│ ├── Start-IDI.ps1
│ ├── Restore-IDI.ps1
│ ├── ConvertTo-IDINotes.ps1
│ ├── Import-IDIAppConnection.ps1
│ ├── Invoke-IDIDeviceSync.ps1
│ ├── Invoke-IDIDeviceRestart.ps1
│ ├── Invoke-IDIDeviceBitLockerRotation.ps1
│ ├── Invoke-IDIDeviceWipe.ps1
│ ├── Invoke-IDIDeviceDelete.ps1
│ ├── Invoke-IDIDeviceRetire.ps1
│ ├── New-IDIApp.ps1
│ ├── Invoke-IDIDeviceDefenderScan.ps1
│ ├── Invoke-IDIDeviceDefenderSignatures.ps1
│ ├── Get-noneIDIDevice.ps1
│ └── Get-IDIDevice.ps1
│ ├── ReleaseNotes.md
│ └── readme.md
├── Add-ons
├── DeviceInfo-Agent
│ ├── logic app
│ │ └── assign-managed-identity-permissions.ps1
│ ├── Runbook
│ │ └── azure-automation.ps1
│ └── Proactive Remediation
│ │ └── get-clientDataDetection.ps1
└── readme.md
├── LICENSE.md
└── readme.md
/UI/UI.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/UI.zip
--------------------------------------------------------------------------------
/UI/.images/auth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/.images/auth.png
--------------------------------------------------------------------------------
/UI/.images/actions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/.images/actions.png
--------------------------------------------------------------------------------
/UI/.images/toolView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/.images/toolView.png
--------------------------------------------------------------------------------
/UI/libaries/ControlzEx.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/libaries/ControlzEx.dll
--------------------------------------------------------------------------------
/UI/.images/multiDeviceView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/.images/multiDeviceView.png
--------------------------------------------------------------------------------
/UI/libaries/MahApps.Metro.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/libaries/MahApps.Metro.dll
--------------------------------------------------------------------------------
/UI/libaries/SimpleDialogs.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/libaries/SimpleDialogs.dll
--------------------------------------------------------------------------------
/UI/.images/singleDeviceView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/.images/singleDeviceView.png
--------------------------------------------------------------------------------
/UI/libaries/IntuneDeviceInventory.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/libaries/IntuneDeviceInventory.ico
--------------------------------------------------------------------------------
/UI/libaries/IntuneDeviceInventory.lnk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/UI/libaries/IntuneDeviceInventory.lnk
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/IntuneDeviceInventory.psd1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/Module/IntuneDeviceInventory/IntuneDeviceInventory.psd1
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/IntuneDeviceInventory.psm1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlorianSLZ/IntuneDeviceInventory/HEAD/Module/IntuneDeviceInventory/IntuneDeviceInventory.psm1
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Test-4IDIDevices.ps1:
--------------------------------------------------------------------------------
1 | function Test-4IDIDevices {
2 |
3 | <#
4 | .SYNOPSIS
5 | Test if Get-IDIDevices already run (presence of $global:IDIDevices_all)
6 |
7 | .DESCRIPTION
8 | Test if Get-IDIDevices already run (presence of $global:IDIDevices_all)
9 |
10 | #>
11 |
12 | if(!$global:IDIDevices_all){
13 | Write-Verbose "Devices not retrived yet, will do that for you..."
14 | $global:IDIDevices_all = Get-IDIDevice -All
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Get-noneIDIReference.ps1:
--------------------------------------------------------------------------------
1 | function Get-noneIDIReference{
2 | <#
3 | .SYNOPSIS
4 | Get ReferenceDevice to determine custom fields.
5 |
6 | .DESCRIPTION
7 | Get ReferenceDevice to determine custom fields.
8 |
9 | #>
10 |
11 | if(!$global:ReferenceDevice){
12 | Write-Verbose "Get first managed Devices as reference from Intune..."
13 | $uri = 'https://graph.microsoft.com/beta/deviceManagement/managedDevices?$top=1'
14 | $global:ReferenceDevice = (Invoke-MgGraphRequest -Method GET -Uri $uri).value | Select-Object -First 1
15 | }
16 | return $global:ReferenceDevice
17 | }
--------------------------------------------------------------------------------
/Add-ons/DeviceInfo-Agent/logic app/assign-managed-identity-permissions.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Modules AzureAD
2 |
3 | # Managed Identiy Object ID
4 | $MI_objid = Read-Host "Object (principal) ID (ex. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
5 | # Microsoft Graph App ID
6 | $AppID = "00000003-0000-0000-c000-000000000000"
7 | # API permissions
8 | $permissions = "DeviceManagementManagedDevices.ReadWrite.All"
9 |
10 | # Connect to Azure AD
11 | Connect-AzureAD
12 |
13 | # Get service principal of the Managed Identiy
14 | $app = Get-AzureADServicePrincipal -Filter "AppID eq '$AppID'"
15 |
16 | # Assign permissions to the Managed Identity service principal
17 | foreach ($permission in $permissions){
18 | $role = $app.AppRoles | Where-Object Value -Like $permission | Select-Object -First 1
19 | New-AzureADServiceAppRoleAssignment -Id $role.Id -ObjectId $MI_objid -PrincipalId $MI_objid -ResourceId $app.ObjectId
20 | }
21 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Add-IDIProperty.ps1:
--------------------------------------------------------------------------------
1 | function Add-IDIProperty{
2 |
3 | <#
4 | .SYNOPSIS
5 | Add a new property to the device collection.
6 |
7 | .DESCRIPTION
8 | Add a new property to the device collection.
9 |
10 | .PARAMETER PropertyName
11 | Specify the name of the new property. (Will be stored in the devices notes field)
12 |
13 | #>
14 |
15 | param (
16 | [parameter(Mandatory = $true, HelpMessage = "Specify the name of the new property.")]
17 | [ValidateNotNullOrEmpty()]
18 | [string]$PropertyName
19 | )
20 |
21 | if($ObjNotes.PSObject.Properties.Name -contains $PropertyName){
22 | Write-Warning "Property already present"
23 | }
24 | else{
25 | if(!$global:IDIDevices_all){$global:IDIDevices_all = (Get-IDIDevice -All) }
26 | $global:IDIDevices_all | Add-Member -NotePropertyName $PropertyName -NotePropertyValue $null
27 | }
28 | }
--------------------------------------------------------------------------------
/Add-ons/readme.md:
--------------------------------------------------------------------------------
1 | |Florian Salzmann|[](https://twitter.com/FlorianSLZ/) [](https://www.linkedin.com/in/fsalzmann/) [](https://scloud.work/en/about)|
2 | |----------------|-------------------------------|
3 | |**Jannik Reinhard**|[](https://twitter.com/jannik_reinhard) [](https://www.linkedin.com/in/jannik-r/) [](https://jannikreinhard.com/)|
4 |
5 | # IntuneDeviceInventory Add-ons
6 |
7 | We are working on our first addon für the IntuneDeviceInventory.
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Set-IDIDevice.ps1:
--------------------------------------------------------------------------------
1 | function Set-IDIDevice{
2 |
3 | <#
4 | .SYNOPSIS
5 | Set/Update device with changed properties (Notes field)
6 |
7 | .DESCRIPTION
8 | Set/Update device with changed properties (Notes field)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to set/update
12 |
13 | .PARAMETER all
14 | Switch to run command for all devices
15 |
16 | #>
17 |
18 | param (
19 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to set/update")]
20 | [ValidateNotNullOrEmpty()]
21 | [array]$IDIDevice,
22 |
23 | [parameter(Mandatory = $false, HelpMessage = "Switch to run command for all devices")]
24 | [ValidateNotNullOrEmpty()]
25 | [switch]$all
26 |
27 | )
28 |
29 |
30 | if($all){
31 | $global:IDIDevices_all | ForEach-Object{ Set-IDIDevice -IDIDevice $_}
32 | }elseif(!$IDIDevice){
33 | Write-Warning "No device specified."
34 | }else{
35 | Set-IDIDeviceNotes -IDIDevice $IDIDevice
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) [2022] [Florian Salzmann]
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.
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-PagingRequest.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-PagingRequest {
2 |
3 | <#
4 | .SYNOPSIS
5 | Invoke Graph API reguest with paging
6 |
7 | .DESCRIPTION
8 | Invoke Graph API reguest with paging
9 |
10 | .PARAMETER URI
11 | Graph API uri
12 |
13 | .PARAMETER Method
14 | Graph API methode
15 |
16 |
17 | #>
18 |
19 | param (
20 | [parameter(Mandatory = $true, HelpMessage = "Array of the device to trigger an Intune Device sync")]
21 | [ValidateNotNullOrEmpty()]
22 | [string]$URI,
23 |
24 | [parameter(Mandatory = $true, HelpMessage = "Get Intune Devices within a specific group")]
25 | [ValidateNotNullOrEmpty()]
26 | [string]$Method
27 |
28 | )
29 |
30 | $GraphResponse = Invoke-MgGraphRequest -Method $Method -Uri $uri
31 |
32 | $GraphResponseCollection = $GraphResponse.value
33 | $UserNextLink = $GraphResponse."@odata.nextLink"
34 |
35 |
36 | while($UserNextLink -ne $null){
37 |
38 | $GraphResponse = (Invoke-MgGraphRequest -Uri $UserNextLink -Method $Method)
39 | $UserNextLink = $GraphResponse."@odata.nextLink"
40 | $GraphResponseCollection += $GraphResponse.value
41 |
42 | }
43 |
44 | return $GraphResponseCollection
45 |
46 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Remove-IDIAppConnection.ps1:
--------------------------------------------------------------------------------
1 | function Remove-IDIAppConnection {
2 | <#
3 | .SYNOPSIS
4 | Save the App Connection details
5 |
6 | .DESCRIPTION
7 | Save the App Connection details
8 |
9 | .PARAMETER All
10 | Switch to delete all App connections
11 |
12 | .PARAMETER TenantId
13 | TenantId for connection with MSGraph
14 |
15 | .PARAMETER Path
16 | Path where the App connections are stored
17 |
18 | #>
19 |
20 | param (
21 | [parameter(Mandatory = $false, HelpMessage = "TenantId of the App connection")]
22 | [ValidateNotNullOrEmpty()]
23 | [string]$TenantId = "*",
24 |
25 | [parameter(Mandatory = $false, HelpMessage = "Switch to delete all App connections")]
26 | [ValidateNotNullOrEmpty()]
27 | [switch]$All,
28 |
29 | [parameter(Mandatory = $false, HelpMessage = "Path where the App connections are stored")]
30 | [ValidateNotNullOrEmpty()]
31 | [string]$Path = "$env:LocalAppData\IntuneDeviceInventory\AppConnection\$TenantId.connection"
32 | )
33 |
34 | if($All){
35 | Write-Verbose "Removing all App connections: $Path"
36 | Remove-Item -Path "$Path" -Force
37 | }else{
38 | Write-Verbose "Removing App connection: $Path"
39 | Remove-Item -Path "$Path" -Force
40 | }
41 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Set-IDIDeviceNotes.ps1:
--------------------------------------------------------------------------------
1 | function Set-IDIDeviceNotes{
2 |
3 | <#
4 | .SYNOPSIS
5 | Set/Update device with changed properties (Notes field)
6 |
7 | .DESCRIPTION
8 | Set/Update device with changed properties (Notes field)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to set/update
12 |
13 | #>
14 |
15 | param (
16 | [parameter(Mandatory = $true, HelpMessage = "Array of the device to set/update")]
17 | [ValidateNotNullOrEmpty()]
18 | [array]$IDIDevice
19 |
20 | )
21 | try{
22 | $id = $IDIDevice.id
23 | $Notesonly = $($IDIDevice[0] | Convertto-Json) | Convertfrom-Json
24 | $RefProperties = (Get-noneIDIReference).PSObject.Properties.Name
25 | foreach($Property in $RefProperties){
26 | $Notesonly[0].PSObject.Properties.Remove("$Property")
27 | }
28 | Write-Verbose "Update Device $id"
29 | Write-Verbose " Properties: $($Notesonly.PSObject.Properties.Name)"
30 |
31 | #Update the notes of the device
32 | $Note_json = $Notesonly | Convertto-Json
33 | $Json = @{ "notes" = "$Note_json" }
34 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$id')"
35 |
36 | Invoke-MgGraphRequest -Uri $uri -Method PATCH -Body $Json
37 |
38 | }catch{
39 | Write-Error $_
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/ReleaseNotes.md:
--------------------------------------------------------------------------------
1 | # Release notes for IntuneDeviceInventory module
2 |
3 | ## 1.3.0
4 | Moved away from Microsoft.Graph.Intune Module
5 |
6 | ## 1.2.0
7 | Added Backup & Restore functions:
8 | - Backup-IDI
9 | - Restore-IDI
10 |
11 | ## 1.1.1
12 | Minor fixes
13 |
14 | ## 1.1.0
15 | Added *-Progress* to Get-IDIDevices to show the process of reading notes
16 |
17 | Added Invoke functions with Major impact:
18 | - Invoke-IDIDeviceWipe
19 | - Invoke-IDIDeviceRetire
20 | - Invoke-IDIDeviceDelete
21 |
22 | ## 1.0.1
23 | - minor changes
24 | - "-Verbose" optimization
25 |
26 | ## 1.0.0
27 | This is the relese after testing and optimizing varios funtions.
28 |
29 | Added functions:
30 | - ConvertTo-IDINotes
31 |
32 | ## 0.1.3
33 | Added functions:
34 | - Import-IDIAppConnection
35 | - Invoke-PagingRequest
36 | - New-IDIApp
37 | - Save-IDIAppConnection
38 |
39 | Improved funtions:
40 | - Connect-IDI
41 | - Support for Azure AD App authentification
42 | - Get-IDIDevices
43 | - Support for 1000+ Devices
44 |
45 | ## 0.1.2
46 | Added functions:
47 | - Get-IDIDeviceNotes
48 | - Invoke-IDIDeviceBitLockerRotation
49 | - Invoke-IDIDeviceDefenderScan
50 | - Invoke-IDIDeviceDefenderSignatures
51 | - Invoke-IDIDeviceRestart
52 | - Invoke-IDIDeviceSync (changed from Sync-IDIDevice)
53 | - Set-IDIDeviceNotes
54 | - Start-IDI
55 |
56 | ## 0.1.1
57 | Fixes and performance optimisation
58 |
59 | ## 0.1.0
60 | - Initial release, se README.md for documentation.
--------------------------------------------------------------------------------
/UI/Install-IntuneDeviceInventoryUI.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | Version: 1.0
3 | Author: Florian Salzman (scloud.work) / Jannik Reinhard (jannikreinhard.com)
4 | Script: Install_IntuneDeviceInventoryUI
5 | Description:
6 | Installation of the intune device inventory
7 | Release notes:
8 | 1.0 :
9 | - Init
10 | #>
11 |
12 | # Program variables
13 | $ProgramPath = "$env:LOCALAPPDATA\IntuneDeviceInventory"
14 |
15 | #############################################################################################################
16 | # Program files
17 | #############################################################################################################
18 |
19 | try{
20 | # Copy Files & Folders
21 | Write-Host "Copying / updating program files..."
22 | New-Item $ProgramPath -type Directory -Force | Out-Null
23 | Copy-Item $($(Split-Path $MyInvocation.MyCommand.Path) + "\*") $ProgramPath -Force -Recurse
24 | Get-Childitem -Recurse $ProgramPath | Unblock-file
25 | Write-Host "Program files completed" -ForegroundColor green
26 |
27 | # Create Startmenu shortcut
28 | Write-Host "Creating / updating startmenu shortcut..."
29 | Copy-Item "$ProgramPath\libaries\IntuneDeviceInventory.lnk" "$env:appdata\Microsoft\Windows\Start Menu\Programs\IntuneDeviceInventory.lnk" -Force -Recurse
30 | Write-Host "Startmenu item completed" -ForegroundColor green
31 |
32 | }catch{$_}
33 |
34 | # Enter to exit
35 | Write-Host "Installation completed!" -ForegroundColor green
36 | Read-Host "Press [Enter] to close"
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | |Florian Salzmann|[](https://twitter.com/FlorianSLZ/) [](https://www.linkedin.com/in/fsalzmann/) [](https://scloud.work/en/about)|
2 | |----------------|-------------------------------|
3 | |**Jannik Reinhard**|[](https://twitter.com/jannik_reinhard) [](https://www.linkedin.com/in/jannik-r/) [](https://jannikreinhard.com/)|
4 |
5 | # IntuneDeviceInventory (PowerShell Module & UI)
6 |
7 | This repository is divided into two parts. The module itself and its UI.
8 |
9 | | Module | UI |
10 | |----------------|-------------------------------|
11 | | [Module @GitHub](https://github.com/FlorianSLZ/IntuneDeviceInventory/tree/main/Module) | [UI @GitHub](https://github.com/FlorianSLZ/IntuneDeviceInventory/tree/main/UI) |
12 | | [Module Introduction](https://scloud.work/IntuneDeviceInventory) | [UI Introduction](https://jannikreinhard.com/2022/11/13/intune-device-inventory-ui/) |
13 |
14 | # Functions
15 | The module and UI was created to have the ability to add more pieces of information to a Microsoft Intune device object.
16 | In addition, there are some functions to bulk initiate Intune commands like a Sync or BitLocker key rotations for devices.
17 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Get-IDIDeviceNotes.ps1:
--------------------------------------------------------------------------------
1 | function Get-IDIDeviceNotes{
2 | <#
3 | .SYNOPSIS
4 | Get Intune Device and compile notes (json) into the output
5 |
6 | .DESCRIPTION
7 | Get Intune Device and compile notes (json) into the output
8 |
9 | .PARAMETER IDIDevice
10 | Array of the device to get the notes from
11 |
12 | #>
13 |
14 | param (
15 | [parameter(Mandatory = $true, HelpMessage = "Array of the device to get the notes from")]
16 | [ValidateNotNullOrEmpty()]
17 | [array]$IDIDevice
18 |
19 | )
20 |
21 | # Get notes field
22 | $Notes = @()
23 | $Resource = "deviceManagement/managedDevices('$($IDIDevice.id)')"
24 | $properties = 'notes'
25 | $uri = "https://graph.microsoft.com/beta/$($Resource)?select=$properties"
26 | $Notes = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).notes
27 |
28 | if($Notes){
29 | try{
30 | $Notes = $Notes | Convertfrom-Json
31 |
32 | # add Properties to main collection & this client
33 | foreach($property in $Notes[0].PSObject.Properties){
34 | if($global:IDIDevices_all){
35 | if (-not ($global:IDIDevices_all | Get-Member -Name $property.Name)){
36 | $global:IDIDevices_all | Add-Member -NotePropertyName $property.Name -NotePropertyValue $null
37 | }
38 | }
39 |
40 | $IDIDevice | Add-Member -NotePropertyName $property.Name -NotePropertyValue $property.Value -Force
41 |
42 | }
43 | }catch{
44 | Write-Error "Notes of $($IDIDevice.id) are not compatible with the IntuneDeviceInventory. `nRun the following command to convert them:`nConvertTo-IDINotes -DeviceId $($IDIDevice.id)"
45 | }
46 |
47 | }
48 | return $IDIDevice
49 |
50 | }
--------------------------------------------------------------------------------
/UI/xaml/message.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Connect-IDI.ps1:
--------------------------------------------------------------------------------
1 | function Connect-IDI {
2 | <#
3 | .SYNOPSIS
4 | Connect to the MSGraph
5 |
6 | .DESCRIPTION
7 | Connect to the MSGraph
8 |
9 | .PARAMETER ClientId
10 | AppID for connection with MSGraph
11 |
12 | .PARAMETER ClientSecret
13 | App Secret for connection with MSGraph
14 |
15 | .PARAMETER TenantId
16 | TenantId for connection with MSGraph
17 |
18 | #>
19 |
20 | param (
21 | [parameter(Mandatory = $false, HelpMessage = "AppId for connection with MSGraph")]
22 | [ValidateNotNullOrEmpty()]
23 | [string]$ClientId,
24 |
25 | [parameter(Mandatory = $false, HelpMessage = "TenantId for connection with MSGraph")]
26 | [ValidateNotNullOrEmpty()]
27 | [string]$TenantId,
28 |
29 | [parameter(Mandatory = $false, HelpMessage = "App Secret for connection with MSGraph")]
30 | [ValidateNotNullOrEmpty()]
31 | [string]$ClientSecret
32 | )
33 |
34 | if($ClientId -and $ClientSecret -and $TenantId){
35 | Write-Verbose "Graph connection via Entra App, Tenant: $TenantId"
36 | $ClientSecretCredential = New-Object System.Management.Automation.PSCredential ($ClientId, $(ConvertTo-SecureString $ClientSecret -AsPlainText -Force))
37 | Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential
38 |
39 | }else{
40 | # Disconnect old session
41 | if($(Get-MgContext).AppName){
42 | Write-Host "Kill old Graph Session"
43 | Disconnect-Graph
44 | }
45 |
46 | Write-Verbose "Graph connection via user authentification"
47 | $MSGraph = Connect-MgGraph -Scopes "User.Read.All", "Device.Read.All", "DeviceManagementManagedDevices.ReadWrite.All", "DeviceManagementServiceConfig.ReadWrite.All", "GroupMember.ReadWrite.All"
48 | Write-Verbose $MSGraph
49 |
50 | }
51 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Save-IDIAppConnection.ps1:
--------------------------------------------------------------------------------
1 | function Save-IDIAppConnection {
2 | <#
3 | .SYNOPSIS
4 | Save the App Connection details
5 |
6 | .DESCRIPTION
7 | Save the App Connection details
8 |
9 | .PARAMETER ClientId
10 | ClientID for connection with MSGraph
11 |
12 | .PARAMETER TenantId
13 | TenantId for connection with MSGraph
14 |
15 | .PARAMETER ClientSecret
16 | ClientSecret for connection with MSGraph
17 |
18 | .PARAMETER Path
19 | Path where the App Connection details will be saved
20 |
21 | #>
22 |
23 | param (
24 | [parameter(Mandatory = $true, HelpMessage = "ClientId for connection with MSGraph")]
25 | [ValidateNotNullOrEmpty()]
26 | [string]$ClientId,
27 |
28 | [parameter(Mandatory = $true, HelpMessage = "TenantId for connection with MSGraph")]
29 | [ValidateNotNullOrEmpty()]
30 | [string]$TenantId,
31 |
32 | [parameter(Mandatory = $true, HelpMessage = "Client Secret for connection with MSGraph")]
33 | [ValidateNotNullOrEmpty()]
34 | [string]$ClientSecret,
35 |
36 | [parameter(Mandatory = $false, HelpMessage = "Path where the App connections are stored")]
37 | [ValidateNotNullOrEmpty()]
38 | [string]$Path = "$env:LocalAppData\IntuneDeviceInventory\AppConnection\$TenantId.connection"
39 | )
40 |
41 |
42 | Write-Verbose "Create and save connection details for $TenantId ..."
43 | # Creat Connection Infos
44 | $AADApp_connection = New-Object psobject -Property @{
45 | TenantId = $TenantId;
46 | ClientId = $ClientId;
47 | ClientSecret = $($ClientSecret | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString)
48 | }
49 |
50 | New-Item -ItemType Directory -Path "$env:LocalAppData\IntuneDeviceInventory\AppConnection\" -Force | Out-Null
51 | Export-Clixml -InputObject $AADApp_connection -Path $Path -Force
52 |
53 |
54 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Backup-IDI.ps1:
--------------------------------------------------------------------------------
1 | function Backup-IDI{
2 |
3 | <#
4 | .SYNOPSIS
5 | Backup all custom fields (Notes) including the device id an serialnumber
6 |
7 | .DESCRIPTION
8 | Set/Update device with changed properties (Notes field)
9 |
10 | .PARAMETER Path
11 | Path where the backup will be stored
12 |
13 | .PARAMETER open
14 | Switch to open the folder where the backup is stored
15 |
16 | #>
17 |
18 | param (
19 | [parameter(Mandatory = $false, HelpMessage = "Path where the backup will be stored")]
20 | [ValidateNotNullOrEmpty()]
21 | [string]$Path = "$env:temp\IntuneDeviceInventory\IntuneDeviceInventory-$(Get-Date -Format yyyy-MM-dd).json",
22 |
23 | [parameter(Mandatory = $false, HelpMessage = "Switch to open the folder where the backup is stored")]
24 | [ValidateNotNullOrEmpty()]
25 | [switch]$open
26 |
27 | )
28 |
29 | try{
30 | $Backup = @()
31 | Write-Verbose "Reading all devices"
32 | $AllDevices = Get-IDIDevice -All
33 |
34 | foreach($Device in $AllDevices){
35 |
36 | Write-Verbose "Backup Config from Device: $($Device.id)"
37 | $Device_backup = $($Device[0] | Convertto-Json) | Convertfrom-Json
38 | $RefProperties = (Get-noneIDIReference).PSObject.Properties.Name
39 | foreach($Property in $RefProperties){
40 | if(($Property -ne "id") -and ($Property -ne "serialNumber")){
41 | $Device_backup[0].PSObject.Properties.Remove("$Property")
42 | }
43 | }
44 | $Backup += $Device_backup
45 | }
46 |
47 | # Creat backup file
48 | Write-Verbose "Create Backup at: $Path"
49 | New-Item -Path $Path -Force | Out-Null
50 | $Backup | ConvertTo-Json | Out-File $Path -Encoding utf8 -Force
51 |
52 | Write-Host "Backup completet:" -ForegroundColor Green
53 | Write-Host $Path
54 |
55 | if($open){
56 | Invoke-Item $(Split-Path -Path $Path)
57 | }
58 |
59 | }catch{
60 | Write-Error $_
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Start-IDI.ps1:
--------------------------------------------------------------------------------
1 | function Start-IDI{
2 | <#
3 | .SYNOPSIS
4 | Get all Intune Devices and compile notes (json) into the output
5 |
6 | .DESCRIPTION
7 | Get all Intune Devices and compile notes (json) into the output
8 |
9 | .PARAMETER outFile
10 | Switch to create a JSON file after command completion
11 |
12 | .PARAMETER IDIDevices_json
13 | Path to the Output JSON
14 |
15 | .PARAMETER openJSON
16 | Switch to open the JSON file after creation
17 |
18 | .PARAMETER Force
19 | Switch to force overwrite of cached changes
20 |
21 | #>
22 |
23 | param (
24 | [parameter(Mandatory = $false, HelpMessage = "Switch to create a JSON file after command completion")]
25 | [ValidateNotNullOrEmpty()]
26 | [switch]$outFile,
27 |
28 | [parameter(Mandatory = $false, HelpMessage = "Path to the Output JSON")]
29 | [ValidateNotNullOrEmpty()]
30 | [string]$IDIDevices_json = "$env:temp\IDIDevices.json",
31 |
32 | [parameter(Mandatory = $false, HelpMessage = "Switch to open the JSON file after creation")]
33 | [ValidateNotNullOrEmpty()]
34 | [switch]$openJSON,
35 |
36 | [parameter(Mandatory = $false, HelpMessage = "Switch to force overwrite of cached changes")]
37 | [ValidateNotNullOrEmpty()]
38 | [switch]$Force,
39 |
40 | [parameter(Mandatory = $false, HelpMessage = "Switch to run the function silent, without any output")]
41 | [ValidateNotNullOrEmpty()]
42 | [switch]$Silent
43 |
44 | )
45 |
46 |
47 | # Run again, will overrite
48 | if(!$Force){
49 | if($global:IDIDevices_all){
50 | if([System.Windows.Forms.MessageBox]::Show("Continue Task? (-Force)","All changes wil be overriten", "YesNo" , "Warning" , "Button1") -ne "Yes"){break}
51 | }
52 | }
53 |
54 | # Check Connection / Connect
55 | Connect-IDI
56 |
57 | $global:IDIDevices_all = Get-IDIDevice -All
58 |
59 | if($outFile){
60 | $global:IDIDevices_all | Convertto-Json | Out-File $IDIDevices_json
61 | if($openJSON){explorer $IDIDevices_json}
62 | }
63 | }
--------------------------------------------------------------------------------
/Add-ons/DeviceInfo-Agent/Runbook/azure-automation.ps1:
--------------------------------------------------------------------------------
1 | # Test content
2 | <#
3 |
4 | $IDI_Intune = '
5 | {
6 | "Monitor": "Dell 24inc",
7 | "Mouse": "Logitech MX",
8 | "purchade date": "2020.01.23",
9 | "memory": "15.8 GB",
10 | "wifiConnectionActive": "False"
11 | }
12 | ' | ConvertFrom-Json
13 |
14 |
15 | $WebhookData = '
16 | {
17 | "data": {
18 | "hostname": "SCLOUD-FLORIAN",
19 | "processor": "11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz",
20 | "memory": "15.8 GB",
21 | "wifiConnectionActive": "False"
22 | },
23 | "validation": {
24 | "aadDeviceId": "99acf826-7869-49cf-891d-51b2c7665686",
25 | "aadDeviceJoinDate": "07/03/2022 11:41:08",
26 | "tenantId": "acc82e89-04ec-460e-a300-f962ff2c4901"
27 | },
28 | "time": "01/15/2023 16:15:35"
29 | }
30 | '
31 |
32 | #>
33 |
34 | param
35 | (
36 | [Parameter(Mandatory=$false)]
37 | [object] $WebhookData
38 | )
39 |
40 | $Data = $WebhookData | ConvertFrom-Json
41 | $Validation = $Data.validation
42 | $IDI_local = $Data.data
43 |
44 | # Check Teannt ID
45 | $uri = "https://graph.microsoft.com/v1.0/organization"
46 | $TenantInfo = (Invoke-MSGraphRequest -HttpMethod GET -Url $uri -ErrorAction Stop).value
47 | if($TenantInfo.id -eq $Validation.tenantId){
48 | Write-Output "Tenant ID matched: $($TenantInfo.id) ..."
49 | }else{
50 | Write-Error "Wrong Tenant ID in request: $($Validation.tenantId)"
51 | break
52 | }
53 |
54 |
55 | # Get Intune Device by aadDeviceID
56 | $IDI_Intune = Get-IDIDevice -azureADDeviceId $Validation.aadDeviceId
57 |
58 | if($IDI_Intune.enrolledDateTime -eq $Validation.aadDeviceJoinDate){
59 | Write-Output "AAD Device ID and enrollemt Date matched."
60 | }else{
61 | Write-Error "Wrong Validation in request: $($Validation)"
62 | break
63 | }
64 |
65 | # Compare and add or set notes
66 | $properties_local = $IDI_local | Get-Member -MemberType NoteProperty
67 | foreach($property in $properties_local){
68 | if (-not ($IDI_Intune | Get-Member -Name $property.Name)){
69 | $IDI_Intune | Add-Member -NotePropertyName $property.Name -NotePropertyValue $IDI_local.($property.Name)
70 | }else{
71 | $IDI_Intune.($property.Name) = $IDI_local.($property.Name)
72 | }
73 | }
74 |
75 | # save notes in Intune
76 | Set-IDIDevice -IDIDevice $IDI_Intune
--------------------------------------------------------------------------------
/UI/xaml/authSelection.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | User auth
31 | Serivce Principle (insert manual)
32 | Serivce Principle (auto creation and save local)
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Restore-IDI.ps1:
--------------------------------------------------------------------------------
1 | function Restore-IDI{
2 |
3 | <#
4 | .SYNOPSIS
5 | Restore custom fields (Notes)
6 |
7 | .DESCRIPTION
8 | Restore custom fields (Notes)
9 | Assigment possible by id (Intune) or serialnumber
10 |
11 | .PARAMETER Path
12 | Path where the backup is stored
13 |
14 | .PARAMETER serial
15 | Switch to restore by serialnumber (default is id)
16 |
17 | #>
18 |
19 | param (
20 | [parameter(Mandatory = $true, HelpMessage = "Path where the backup is stored")]
21 | [ValidateNotNullOrEmpty()]
22 | [string]$Path,
23 |
24 | [parameter(Mandatory = $false, HelpMessage = "Switch to restore by serialnumber (default is id)")]
25 | [ValidateNotNullOrEmpty()]
26 | [switch]$serial
27 |
28 | )
29 |
30 | try{
31 | if(Test-Path -Path $Path){
32 | Write-Verbose "Reading Backup from: $Path"
33 | $BackupJson = Get-Content $Path
34 | $Backup = $BackupJson | ConvertFrom-Json
35 |
36 | foreach($Device in $Backup){
37 | if($serial){
38 | Write-Verbose "Get managed Device from Intune by SerialNumber: $($Device.serialNumber) ..."
39 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=serialNumber%20eq%20'$($Device.serialNumber)'"
40 | $Device.id = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value.id
41 | }
42 |
43 | if($Device.id){
44 | Write-Verbose "Restore IDI / Notes for: $($Device.id)"
45 | $Notesonly = $($Device[0] | Convertto-Json) | Convertfrom-Json
46 | $Notesonly[0].PSObject.Properties.Remove("id")
47 | $Notesonly[0].PSObject.Properties.Remove("serialNumber")
48 |
49 | Write-Verbose " Properties: $($Notesonly.PSObject.Properties.Name)"
50 |
51 | # Update the notes of the device
52 | $Note_json = $Notesonly | Convertto-Json
53 | $Json = @{ "notes" = "$Note_json" }
54 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($Device.id)')"
55 |
56 | Invoke-MgGraphRequest -Uri $uri -Method PATCH -Body $Json
57 | }else{
58 | Write-Warning "Device ID for SerialNumber not found: $($Device.serialNumber)"
59 | }
60 |
61 |
62 | }
63 |
64 | }else{
65 | Write-Error "File does not exist: $Path"
66 | }
67 | }catch{
68 | Write-Error $_
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/ConvertTo-IDINotes.ps1:
--------------------------------------------------------------------------------
1 | function ConvertTo-IDINotes{
2 |
3 | <#
4 | .SYNOPSIS
5 | Converts non IDI created Notes to IDI compatible JSON with custom PropertyName
6 |
7 | .DESCRIPTION
8 | Converts non IDI created Notes to IDI compatible JSON with custom PropertyName
9 |
10 | .PARAMETER DeviceId
11 | Array of the device to set/update
12 |
13 | .PARAMETER PropertyName
14 | Property Name of the notes content
15 |
16 | .PARAMETER All
17 | Switch to run command for all devices
18 |
19 | #>
20 |
21 | param (
22 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to converte the notes for")]
23 | [ValidateNotNullOrEmpty()]
24 | [array]$DeviceId,
25 |
26 | [parameter(Mandatory = $true, HelpMessage = "Property Name of the notes content")]
27 | [ValidateNotNullOrEmpty()]
28 | [string]$PropertyName,
29 |
30 | [parameter(Mandatory = $false, HelpMessage = "Convert Notes from all Devices")]
31 | [ValidateNotNullOrEmpty()]
32 | [switch]$All
33 |
34 | )
35 | try{
36 | if($All){
37 | Write-Verbose "Read all Intune Devices and run *ConvertTo-IDINotes -IDIDevice `$_* ..."
38 | Get-noneIDIDevice -All | ForEach-Object{ ConvertTo-IDINotes -DeviceId $_.id -PropertyName $PropertyName }
39 |
40 | }else{
41 | Write-Verbose "Read notes for $DeviceId"
42 | $Notes = $null
43 | $Resource = "deviceManagement/managedDevices('$DeviceId')"
44 | $properties = 'notes'
45 | $uri = "https://graph.microsoft.com/beta/$($Resource)?select=$properties"
46 | $Notes = (Invoke-MgGraphRequest Method GET -Uri $uri -ErrorAction Stop).notes
47 |
48 | if($Notes){
49 | try{
50 | $IDINoteCheck = $null
51 | $IDINoteCheck = $($Notes | ConvertFrom-Json -ErrorAction SilentlyContinue)
52 | }catch{}
53 |
54 | if($IDINoteCheck){
55 | Write-Warning "Device already compatible with IDI: $DeviceId"
56 |
57 | }else{
58 | Write-Verbose "Convert notes to JSON with PropertyName $PropertyName"
59 | $NoteObj = @(
60 | [pscustomobject]@{$PropertyName="$Notes"}
61 | )
62 | $NoteObj = $NoteObj | Convertto-Json
63 |
64 | Write-Verbose "Update notes on Intune Device: $DeviceId"
65 | $Json = @{ "notes" = "$NoteObj" }
66 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceId')"
67 |
68 | Invoke-MgGraphRequest -Uri $uri -Method PATCH -Body $Json
69 |
70 | }
71 |
72 | }else{Write-Verbose "Device $DeviceId has no notes."}
73 | }
74 | }catch{
75 | Write-Error "Error while processing notes: $DeviceId `n$_"
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Import-IDIAppConnection.ps1:
--------------------------------------------------------------------------------
1 | function Import-IDIAppConnection {
2 | <#
3 | .SYNOPSIS
4 | Get the App Connection details
5 |
6 | .DESCRIPTION
7 | Get the App Connection details
8 |
9 | .PARAMETER ClientId
10 | AppID for connection with MSGraph
11 |
12 | .PARAMETER TenantId
13 | TenantId for connection with MSGraph
14 |
15 | .PARAMETER ClientSecret
16 | ClientSecret for connection with MSGraph
17 |
18 | .PARAMETER Path
19 | Path where the App Connection details are stored
20 |
21 | #>
22 |
23 | param (
24 | [parameter(Mandatory = $false, HelpMessage = "App Secret for connection with MSGraph")]
25 | [ValidateNotNullOrEmpty()]
26 | [string]$Path = "$env:LocalAppData\IntuneDeviceInventory\AppConnection",
27 |
28 | [parameter(Mandatory = $false, HelpMessage = "App Secret for connection with MSGraph")]
29 | [ValidateNotNullOrEmpty()]
30 | [switch]$All,
31 |
32 | [parameter(Mandatory = $false, HelpMessage = "Select one connection to import from -All")]
33 | [ValidateNotNullOrEmpty()]
34 | [switch]$Select,
35 |
36 | [parameter(Mandatory = $false, HelpMessage = "Connect with the selected connection")]
37 | [ValidateNotNullOrEmpty()]
38 | [bool]$Connect = $true,
39 |
40 | [parameter(Mandatory = $false, HelpMessage = "TenantId for connection with MSGraph")]
41 | [ValidateNotNullOrEmpty()]
42 | [string]$TenantId
43 | )
44 |
45 | if($All){
46 | Write-Verbose "Retriving all connections from: $Path"
47 | $Connections_all = (Get-ChildItem $Path | Where-Object{$_.Name -like "*.connection"}).BaseName
48 | return $Connections_all
49 |
50 | }elseif($Select){
51 | Write-Verbose "Open GridView to select one connection"
52 | $Tenant2connect = Import-IDIAppConnection -All | Out-GridView -OutputMode Single
53 | if($Connect -eq $true){
54 | Import-IDIAppConnection -TenantId $Tenant2connect
55 | }else{
56 | Import-IDIAppConnection -TenantId $Tenant2connect -Connect $false
57 | }
58 |
59 | }elseif($TenantId){
60 | Write-Verbose "Import connection details for $TenantId"
61 | $Connections_selected = Import-Clixml -Path "$Path\$TenantId.connection"
62 |
63 | # inverte SecureString
64 | $ClientSecret_SS = ConvertTo-SecureString $Connections_selected.ClientSecret
65 | $ClientSecret_BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ClientSecret_ss)
66 | $Connections_selected.ClientSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ClientSecret_BSTR)
67 |
68 | if($Connect -eq $true){
69 | Connect-IDI -ClientId $Connections_selected.ClientId -TenantId $Connections_selected.TenantId -ClientSecret $Connections_selected.ClientSecret
70 | }else{
71 | return $Connections_selected
72 | }
73 |
74 | }else{
75 | Write-Warning "No parameter specified"
76 | }
77 | }
--------------------------------------------------------------------------------
/Add-ons/DeviceInfo-Agent/Proactive Remediation/get-clientDataDetection.ps1:
--------------------------------------------------------------------------------
1 | #### Configuration ####
2 | $time = Get-Date
3 | function Get-ValidationInfo{
4 | $AzureADJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo"
5 | $AzureADJoinInfoKey = Get-ChildItem -Path $AzureADJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
6 | if ($AzureADJoinInfoKey -ne $null) {
7 | if ([guid]::TryParse($AzureADJoinInfoKey, $([ref][guid]::Empty))) {
8 | $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=$($AzureADJoinInfoKey)" }
9 | }
10 | else {
11 | $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $AzureADJoinInfoKey }
12 | }
13 | if ($AzureADJoinCertificate -ne $null) {
14 | $AzureADDeviceID = ($AzureADJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", ""
15 | $AzureADJoinDate = ($AzureADJoinCertificate | Select-Object -ExpandProperty "NotBefore")
16 | }
17 | }
18 | $AzureADTenantInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\TenantInfo"
19 | $AzureADTenantID = Get-ChildItem -Path $AzureADTenantInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
20 |
21 |
22 | return @"
23 | {
24 | "aadDeviceId" : "$($AzureADDeviceID)"
25 | ,"aadDeviceJoinDate" : "$(($AzureADJoinDate).ToString("MM/dd/yyyy HH:mm:ss"))"
26 | ,"tenantId" : "$($AzureADTenantID)"
27 | }
28 | "@
29 | }
30 |
31 | function Get-DeviceMemory {
32 | $system = Get-WmiObject -Class Win32_ComputerSystem
33 | return "$([Math]::Round(($system.TotalPhysicalMemory/ 1GB),1)) GB"
34 | }
35 |
36 | function Get-ConnectedDevicesPerClass {
37 | param(
38 | $class
39 | )
40 | return (Get-PnpDevice -PresentOnly | Where-Object {$_.Class -eq $class}).FriendlyName[0]
41 | }
42 |
43 | function Test-ConnectionType {
44 | param(
45 | [Parameter(Mandatory = $true)] $test
46 | )
47 | $adapters = Get-WmiObject -Class Win32_NetworkAdapter | Where-Object { $_.NetConnectionStatus -ne 2 }
48 |
49 | foreach ($adapter in $adapters) {
50 | $nic = Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $_.Index -eq $adapter.Index }
51 | if ($nic.Description -like $test) {
52 | return $True
53 | }
54 | }
55 | return $False
56 | }
57 |
58 | ####### Start #######
59 |
60 | $result = @"
61 | {
62 | "data" : {
63 | "hostname" : "$($env:computername)",
64 | "processor" : "$(Get-ConnectedDevicesPerClass -class 'Processor')",
65 | "memory" : "$(Get-DeviceMemory)",
66 | "wifiConnectionActive" : "$(Test-ConnectionType -test "*Wireless*")"
67 | }
68 | ,"validation" : $(Get-ValidationInfo)
69 | ,"time" : "$($time.ToUniversalTime().ToString("MM/dd/yyyy HH:mm:ss"))"
70 | }
71 | "@ | ConvertFrom-Json
72 |
73 | $result | ConvertTo-Json
74 |
--------------------------------------------------------------------------------
/UI/xaml/authServicePrinciple.xaml:
--------------------------------------------------------------------------------
1 |
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 |
45 |
46 |
--------------------------------------------------------------------------------
/UI/readme.md:
--------------------------------------------------------------------------------
1 | |Florian Salzmann|[](https://twitter.com/FlorianSLZ/) [](https://www.linkedin.com/in/fsalzmann/) [](https://scloud.work/en/about)|
2 | |----------------|-------------------------------|
3 | |**Jannik Reinhard**|[](https://twitter.com/jannik_reinhard) [](https://www.linkedin.com/in/jannik-r/) [](https://jannikreinhard.com/)|
4 |
5 | # IntuneDeviceInventory UI (IDIUI)
6 |
7 | You can find the UI for the Device Inventory Module here. This UI supports you to change/set the custom inventory for single devices but also for multiple devices at the same time but also to trigger device actions.
8 |
9 | 
10 |
11 | ## Installing the UI
12 | In the repo there is an installation wrapper that creates a start menu entry and unblocks the dlls.
13 | To install the UI for the following steps out:
14 | - Download the repository
15 | - Execute the setup script
16 |
17 | ```PowerShell
18 | Install-IntuneDeviceInventoryUI.ps1
19 | ```
20 |
21 | ## Start the UI
22 | - If you have installed the IDIUI then search in the start menue "IntuneDeviceInventory"
23 | - if you not installed the IDIUI than make sure that the dlls are unblocked and execute the Start-IntuneDeviceInventoryUi.ps1
24 |
25 | ## Authentication
26 | You have multiple possibilities for the authentication:
27 | - User auth (Authentication with your current or other user)
28 | - Service Principle (insert manual) (Authentication with an service principle insert the appId, TenantId and secret manual)
29 | - Service Principle (auto creation and sace local) (Automatic creation of a new service principle and store the connection information secure on you device to remember for the next login)
30 |
31 | 
32 |
33 |
34 | ## Features
35 | ### Change custom inventory attribute for single device
36 | You can add, change and delete custom attribute to a single device
37 |
38 | 
39 |
40 | ### Change custom inventory attribute for multiple device
41 | You can add, change and delete custom attribute to a multiple devices device
42 |
43 | 
44 |
45 | ### Trigger deive action
46 | You can trigger device actions like sync, restart, bitlocker rotation, defender scan and defender signature update
47 |
48 | 
--------------------------------------------------------------------------------
/UI/modules/utility.psm1:
--------------------------------------------------------------------------------
1 | <#
2 | Version: 1.0
3 | Author: Florian Salzman (scloud.work) / Jannik Reinhard (jannikreinhard.com)
4 | Script: deviceInventoryHandler
5 | Description:
6 | Utility functions
7 | Release notes:
8 | 1.0 :
9 | - Init
10 | #>
11 | function Import-Dlls {
12 | #Load dll
13 | try {
14 | [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
15 | [System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
16 | [System.Reflection.Assembly]::LoadFrom("$global:Path\libaries\MahApps.Metro.dll") | out-null
17 | [System.Reflection.Assembly]::LoadFrom("$global:Path\libaries\ControlzEx.dll") | out-null
18 | [System.Reflection.Assembly]::LoadFrom("$global:Path\libaries\SimpleDialogs.dll") | out-null
19 | #[System.Reflection.Assembly]::LoadFrom("$global:Path\libaries\LoadingIndicators.WPF.dll") | out-null
20 |
21 | }
22 | catch {
23 | Write-Error "Loading from dll's was not sucessfull: $_"
24 | return $false
25 | }
26 | return $true
27 | }
28 |
29 | function Add-TempFolder {
30 | if (-not (Test-Path "$global:Path\.tmp")) {
31 | New-Item "$global:Path\.tmp" -Itemtype Directory
32 | }
33 | return $true
34 | }
35 |
36 |
37 | function Add-XamlEvent{
38 | param(
39 | [Parameter(Mandatory = $true)]
40 | $object,
41 | [Parameter(Mandatory = $true)]
42 | $event,
43 | [Parameter(Mandatory = $true)]
44 | $scriptBlock
45 | )
46 |
47 | try {
48 | if($object)
49 | {
50 | $object."$event"($scriptBlock)
51 | }
52 | else
53 | {
54 | $global:txtSplashText.Text = "Event $($object.Name) loaded successfully"
55 |
56 | }
57 | }
58 | catch
59 | {
60 | Write-Error "Failed load event $($object.Name). Error:" $_.Exception
61 | }
62 | }
63 |
64 |
65 | ########################################################################################
66 | ########################################### UI ########################################
67 | ########################################################################################
68 | function New-XamlScreen{
69 | param (
70 | [Parameter(Mandatory = $true)]
71 | [String]$xamlPath
72 | )
73 | $inputXML = Get-Content $xamlPath
74 | [xml]$xaml = $inputXML -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^
11 |
12 | function Install-IdiModule{
13 | $IntuneDeviceInventory = Get-InstalledModule -Name "IntuneDeviceInventory" -ErrorAction SilentlyContinue
14 | try{
15 | if(-not ($IntuneDeviceInventory)){
16 | Install-Module -Name IntuneDeviceInventory -Scope CurrentUser -Confirm:$false -Force
17 | }
18 | }catch{
19 | Write-Error "Failed to install Intune Device Inventory from Powershell gallery: $_"
20 | return $false
21 | }
22 | return $true
23 | }
24 | function Get-AllDevices{
25 | try{
26 | return Get-IDIDevice -All
27 | }catch{
28 | Write-Error "Failed to get all devices: $_"
29 | return $false
30 | }
31 | }
32 |
33 | function Get-RefresDevices{
34 | $global:allDevices = Get-AllDevices
35 | $allDevicesGrid = Add-DevicesToGridObject -devices $global:allDevices
36 | Add-DevicesToGrid -devices $allDevicesGrid
37 | }
38 |
39 | function Get-RefreshSingleDevice($Device){
40 | $index = $global:allDevices.id.IndexOf($Device.id)
41 | $global:allDevices[$index] = $Device
42 | $allDevicesGrid = Add-DevicesToGridObject -devices $global:allDevices
43 | Add-DevicesToGrid -devices $allDevicesGrid
44 | }
45 |
46 |
47 | function Add-DevicesToGridObject {
48 | param (
49 | [Parameter(Mandatory = $true)] $devices
50 | )
51 | $managedDevicesGridObjects = @()
52 |
53 | $referenceObject = (Get-noneIDIReference).PSObject.Properties.Name
54 | $devices | ForEach-Object {
55 | try{
56 | $customInventory = @(($_ | Select-Object -Property * -ExcludeProperty $referenceObject).PSObject.Properties | Select-Object -Property Value, Name)
57 | foreach($item in $customInventory){
58 | $item | Add-Member -MemberType NoteProperty -Name "Changed" -Value $null
59 | $item | Add-Member -MemberType NoteProperty -Name "UpdateAttribute" -Value $false
60 | $item | Add-Member -MemberType NoteProperty -Name "InitValue" -Value $item.Value
61 | $item | Add-Member -MemberType NoteProperty -Name "InitName" -Value $item.Name
62 |
63 | if($item.Name -eq '*'){
64 | $item.Name = 'New Attribute'
65 | $item.InitName = $item.Name
66 | $item.Value = 'Add a value'
67 | $item.InitValue = $item.Value
68 | $item.Changed = '(*)'
69 | }
70 | }
71 | }catch{}
72 |
73 | $param = [PSCustomObject]@{
74 | Id = $_.id
75 | AzureAdId = $_.azureActiveDirectoryDeviceId
76 | DeviceName = $_.deviceName
77 | DeviceManagedBy = if($_.managementAgent -eq 'MDM'){'Intune'}else{$_.managementType}
78 | DeviceOwnership = switch ($_.ownerType) {company {'Corporate'} personal{'Personal'} Default {$_.ownerType}}
79 | DeviceCompliance = if($_.complianceState -eq 'noncompliant'){'Not Compliant'}else{'Compliant'}
80 | DeviceOS = switch ($_.deviceType) {windowsRT {'Windows'} macMDM{'macOS'} Default {$_.deviceType}}
81 | DeviceOSVersion = $_.osVersion
82 | DeviceLastCheckin = $_.lastSyncDateTime
83 | DevicePrimaryUser = $_.userPrincipalName
84 | CustomInventory = $customInventory
85 | Details = ($_ | Select-Object -Property $referenceObject)
86 | }
87 | $managedDevicesGridObjects += $param
88 | }
89 | return $managedDevicesGridObjects
90 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceSync.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceSync {
2 |
3 | <#
4 | .SYNOPSIS
5 | Trigger device Sync in Intune
6 |
7 | .DESCRIPTION
8 | Trigger device Sync in Intune
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to set/update
12 |
13 | .PARAMETER Group
14 | Get Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Get Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Get Intune Devices by name
21 |
22 | .PARAMETER id
23 | Get Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Get Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Get all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices out of a GridView
33 |
34 | #>
35 |
36 | param (
37 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger an Intune Device sync")]
38 | [ValidateNotNullOrEmpty()]
39 | [array]$IDIDevice,
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices within a specific group")]
42 | [ValidateNotNullOrEmpty()]
43 | [string]$Group,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices from a specific primary user (UPN)")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$User,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by Device Name")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$deviceName,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by id")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$id,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by azureADDeviceId")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$azureADDeviceId,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Get all Intune Devices")]
62 | [ValidateNotNullOrEmpty()]
63 | [switch]$All,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$Grid
68 |
69 | )
70 |
71 |
72 | if($All){
73 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}}
74 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}}
75 |
76 | }elseif($Group){
77 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
78 |
79 | }elseif($User){
80 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
81 |
82 | }elseif($deviceName){
83 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
84 |
85 | }elseif($id){
86 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
87 |
88 | }elseif($azureADDeviceId){
89 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
90 |
91 | }elseif($Grid){
92 | if($global:IDIDevices_all){
93 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
94 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}
95 | }
96 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceSync -IDIDevice $_}}
97 |
98 | }elseif($IDIDevice){
99 | Write-Verbose "Sync device with id: $($IDIDevice.id) ..."
100 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/microsoft.graph.syncDevice"
101 | Invoke-MgGraphRequest -Uri $uri -Method POST
102 | }else{
103 | Write-Warning "No device or scope for Invoke-IDIDeviceSync specified."
104 | }
105 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceRestart.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceRestart {
2 |
3 | <#
4 | .SYNOPSIS
5 | Trigger device restart in Intune
6 |
7 | .DESCRIPTION
8 | Trigger device restart in Intune
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to restart
12 |
13 | .PARAMETER Group
14 | Restart Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Restart Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Restart Intune Devices by name
21 |
22 | .PARAMETER id
23 | Restart Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Restart Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Restart all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices to restart out of a GridView
33 |
34 | #>
35 |
36 | param (
37 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger an restart")]
38 | [ValidateNotNullOrEmpty()]
39 | [array]$IDIDevice,
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Restart Intune Devices within a specific group")]
42 | [ValidateNotNullOrEmpty()]
43 | [string]$Group,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Restart Intune Devices from a specific user (UPN)")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$User,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Restart Intune Devices by Device Name")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$deviceName,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Restart Intune Devices by id")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$id,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Restart Intune Devices by azureADDeviceId")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$azureADDeviceId,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Restart all Intune Devices")]
62 | [ValidateNotNullOrEmpty()]
63 | [switch]$All,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices to restart out of a GridView")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$Grid
68 |
69 | )
70 |
71 |
72 | if($All){
73 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}}
74 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}}
75 |
76 | }elseif($Group){
77 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
78 |
79 | }elseif($User){
80 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
81 |
82 | }elseif($deviceName){
83 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
84 |
85 | }elseif($id){
86 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
87 |
88 | }elseif($azureADDeviceId){
89 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
90 |
91 | }elseif($Grid){
92 | if($global:IDIDevices_all){
93 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
94 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}
95 | }
96 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceRestart -IDIDevice $_}}
97 |
98 | }elseif($IDIDevice){
99 | Write-Verbose "Trigger restart for device with id: $($IDIDevice.id) ..."
100 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/microsoft.graph.rebootNow"
101 | Invoke-MgGraphRequest -Uri $uri -Method POST
102 | }else{
103 | Write-Warning "No device or scope for Invoke-IDIDeviceRestart specified."
104 | }
105 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceBitLockerRotation.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceBitLockerRotation {
2 |
3 | <#
4 | .SYNOPSIS
5 | Trigger device to rotate BitLocker key in Intune
6 |
7 | .DESCRIPTION
8 | Trigger device to rotate BitLocker key in Intune
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to rotate the BitLocker key
12 |
13 | .PARAMETER Group
14 | Trigger BitLocker key rotation for Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger BitLocker key rotation for Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger BitLocker key rotation for Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger BitLocker key rotation for Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger BitLocker key rotation for Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger BitLocker key rotation for all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices to trigger BitLocker key rotation out of a GridView
33 |
34 | #>
35 |
36 | param (
37 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger an Intune Device sync")]
38 | [ValidateNotNullOrEmpty()]
39 | [array]$IDIDevice,
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices within a specific group")]
42 | [ValidateNotNullOrEmpty()]
43 | [string]$Group,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices from a specific primary user (UPN)")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$User,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by Device Name")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$deviceName,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by id")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$id,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by azureADDeviceId")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$azureADDeviceId,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Get all Intune Devices")]
62 | [ValidateNotNullOrEmpty()]
63 | [switch]$All,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$Grid
68 |
69 | )
70 |
71 |
72 | if($All){
73 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}}
74 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}}
75 |
76 | }elseif($Group){
77 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
78 |
79 | }elseif($User){
80 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
81 |
82 | }elseif($deviceName){
83 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
84 |
85 | }elseif($id){
86 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
87 |
88 | }elseif($azureADDeviceId){
89 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
90 |
91 | }elseif($Grid){
92 | if($global:IDIDevices_all){
93 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
94 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}
95 | }
96 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceBitLockerRotation -IDIDevice $_}}
97 |
98 | }elseif($IDIDevice){
99 | Write-Verbose "Trigger BitLocker key rotation for device with id: $($IDIDevice.id) ..."
100 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/rotateBitLockerKeys"
101 | Invoke-MgGraphRequest -Uri $uri -Method POST
102 | }else{
103 | Write-Warning "No device or scope for Invoke-IDIDeviceBitLockerRotation specified."
104 | }
105 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceWipe.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceWipe {
2 |
3 | <#
4 | .SYNOPSIS
5 | Wipe Intune devices (single or bulk)
6 |
7 | .DESCRIPTION
8 | Wipe Intune devices (single or bulk)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to trigger
12 |
13 | .PARAMETER Group
14 | Trigger Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices out of a GridView
33 |
34 | .PARAMETER Force
35 | Run wipe without asking for each device
36 |
37 | #>
38 |
39 | param (
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger")]
42 | [ValidateNotNullOrEmpty()]
43 | [array]$IDIDevice,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices within a specific group")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$Group,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices from a specific primary user (UPN)")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$User,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by Device Name")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$deviceName,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by id")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$id,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by azureADDeviceId")]
62 | [ValidateNotNullOrEmpty()]
63 | [string]$azureADDeviceId,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Trigger all Intune Devices")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$All,
68 |
69 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
70 | [ValidateNotNullOrEmpty()]
71 | [switch]$Grid,
72 |
73 | [parameter(Mandatory = $false, HelpMessage = "Run wipe without asking for each device")]
74 | [ValidateNotNullOrEmpty()]
75 | [switch]$Force
76 |
77 | )
78 |
79 |
80 | if($All){
81 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}}
82 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}}
83 |
84 | }elseif($Group){
85 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
86 |
87 | }elseif($User){
88 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
89 |
90 | }elseif($deviceName){
91 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
92 |
93 | }elseif($id){
94 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
95 |
96 | }elseif($azureADDeviceId){
97 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
98 |
99 | }elseif($Grid){
100 | if($global:IDIDevices_all){
101 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
102 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}
103 | }
104 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceWipe -IDIDevice $_}}
105 |
106 | }elseif($IDIDevice){
107 |
108 | if(!$Force){
109 | $RunMajor = Read-Host "Are you sure you want to wipe Device $($IDIDevice.id) (Y/N)"
110 | if($RunMajor -ne "Y"){break}
111 | }
112 |
113 | Write-Verbose "Wipe device with id: $($IDIDevice.id) ..."
114 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/microsoft.graph.wipe"
115 | Invoke-MgGraphRequest -Uri $uri -Method POST
116 | }else{
117 | Write-Warning "No device or scope for Invoke-IDIDeviceWipe specified."
118 | }
119 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceDelete.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceDelete {
2 |
3 | <#
4 | .SYNOPSIS
5 | Delete Intune devices (single or bulk)
6 |
7 | .DESCRIPTION
8 | Delete Intune devices (single or bulk)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to trigger
12 |
13 | .PARAMETER Group
14 | Trigger Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices out of a GridView
33 |
34 | .PARAMETER Force
35 | Retire without asking for each device
36 |
37 | #>
38 |
39 | param (
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger")]
42 | [ValidateNotNullOrEmpty()]
43 | [array]$IDIDevice,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices within a specific group")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$Group,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices from a specific primary user (UPN)")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$User,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by Device Name")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$deviceName,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by id")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$id,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by azureADDeviceId")]
62 | [ValidateNotNullOrEmpty()]
63 | [string]$azureADDeviceId,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Trigger all Intune Devices")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$All,
68 |
69 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
70 | [ValidateNotNullOrEmpty()]
71 | [switch]$Grid,
72 |
73 | [parameter(Mandatory = $false, HelpMessage = "Delete without asking for each device")]
74 | [ValidateNotNullOrEmpty()]
75 | [switch]$Force
76 |
77 | )
78 |
79 |
80 | if($All){
81 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}}
82 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}}
83 |
84 | }elseif($Group){
85 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
86 |
87 | }elseif($User){
88 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
89 |
90 | }elseif($deviceName){
91 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
92 |
93 | }elseif($id){
94 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
95 |
96 | }elseif($azureADDeviceId){
97 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
98 |
99 | }elseif($Grid){
100 | if($global:IDIDevices_all){
101 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
102 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}
103 | }
104 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceDelete -IDIDevice $_}}
105 |
106 | }elseif($IDIDevice){
107 |
108 | if(!$Force){
109 | $RunMajor = Read-Host "Are you sure you want to delete Device $($IDIDevice.id) (Y/N)"
110 | if($RunMajor -ne "Y"){break}
111 | }
112 |
113 | Write-Verbose "Deleting device with id: $($IDIDevice.id) ..."
114 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')"
115 | Invoke-MgGraphRequest -Uri $uri -Method DELETE
116 | }else{
117 | Write-Warning "No device or scope for Invoke-IDIDeviceDelete specified."
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceRetire.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceRetire {
2 |
3 | <#
4 | .SYNOPSIS
5 | Retire Intune devices (single or bulk)
6 |
7 | .DESCRIPTION
8 | Retire Intune devices (single or bulk)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to trigger
12 |
13 | .PARAMETER Group
14 | Trigger Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices out of a GridView
33 |
34 | .PARAMETER Force
35 | Retire without asking for each device
36 |
37 | #>
38 |
39 | param (
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger")]
42 | [ValidateNotNullOrEmpty()]
43 | [array]$IDIDevice,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices within a specific group")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$Group,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices from a specific primary user (UPN)")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$User,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by Device Name")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$deviceName,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by id")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$id,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Trigger Intune Devices by azureADDeviceId")]
62 | [ValidateNotNullOrEmpty()]
63 | [string]$azureADDeviceId,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Trigger all Intune Devices")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$All,
68 |
69 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
70 | [ValidateNotNullOrEmpty()]
71 | [switch]$Grid,
72 |
73 | [parameter(Mandatory = $false, HelpMessage = "Retire without asking for each device")]
74 | [ValidateNotNullOrEmpty()]
75 | [switch]$Force
76 |
77 | )
78 |
79 |
80 | if($All){
81 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}}
82 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}}
83 |
84 | }elseif($Group){
85 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
86 |
87 | }elseif($User){
88 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
89 |
90 | }elseif($deviceName){
91 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
92 |
93 | }elseif($id){
94 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
95 |
96 | }elseif($azureADDeviceId){
97 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
98 |
99 | }elseif($Grid){
100 | if($global:IDIDevices_all){
101 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
102 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}
103 | }
104 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceRetire -IDIDevice $_}}
105 |
106 | }elseif($IDIDevice){
107 |
108 | if(!$Force){
109 | $RunMajor = Read-Host "Are you sure you want to retire Device $($IDIDevice.id) (Y/N)"
110 | if($RunMajor -ne "Y"){break}
111 | }
112 |
113 | Write-Verbose "Retire device with id: $($IDIDevice.id) ..."
114 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/microsoft.graph.retire"
115 | Invoke-MgGraphRequest -Uri $uri -Method POST
116 | }else{
117 | Write-Warning "No device or scope for Invoke-IDIDeviceRetire specified."
118 | }
119 | }
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/New-IDIApp.ps1:
--------------------------------------------------------------------------------
1 | function New-IDIApp {
2 | <#
3 | .SYNOPSIS
4 | Create App regestration for Graph API access
5 |
6 | .DESCRIPTION
7 | Create App regestration for Graph API access
8 |
9 | .PARAMETER AppName
10 | AppName for Azure AD registration
11 |
12 | .PARAMETER Save
13 | Path where the App connections are stored
14 |
15 | #>
16 |
17 | param(
18 | [parameter(Mandatory = $false, HelpMessage = "The friendly name of the app registration")]
19 | [ValidateNotNullOrEmpty()]
20 | [String]$AppName = "IntuneDeviceInventory",
21 |
22 | [parameter(Mandatory = $false, HelpMessage = "If used, app credentials will be saved (Save-IDIAppConnection)")]
23 | [ValidateNotNullOrEmpty()]
24 | [switch]$Save,
25 |
26 | [parameter(Mandatory = $false, HelpMessage = "Forces new Key if app exists")]
27 | [ValidateNotNullOrEmpty()]
28 | [switch]$Force,
29 |
30 | [parameter(Mandatory = $false, HelpMessage = "Path where the App connections are stored")]
31 | [ValidateNotNullOrEmpty()]
32 | [string]$Path
33 | )
34 |
35 | Write-Verbose "Checking / installing AzureAD Module ..."
36 | try{
37 | if (!$(Get-Module -ListAvailable -Name "AzureAD*" -ErrorAction SilentlyContinue)) {
38 | Write-Host "Installing Module: AzureAD"
39 | Install-Module "AzureAD" -Scope CurrentUser -Force
40 | }
41 |
42 | }catch{
43 | Write-Error $_
44 | break
45 | }
46 |
47 | $AADConnection = Connect-AzureAD
48 |
49 | if(!($AADApp_obj = Get-AzureADApplication -Filter "DisplayName eq '$($AppName)'" -ErrorAction SilentlyContinue)){
50 | $AADApp_obj = New-AzureADApplication -DisplayName $AppName -AvailableToOtherTenants $false
51 | Write-Verbose $AADApp_obj
52 |
53 | # Add Permissions
54 | # Get current: (Get-AzureADApplication -Filter "DisplayName eq '$($AppName)'").RequiredResourceAccess | ConvertTo-Json -Depth 3
55 | Write-Verbose "Permissions will be set, Admin consent still required"
56 | $Permissions = '
57 | {
58 | "resourceAppId": "00000003-0000-0000-c000-000000000000",
59 | "resourceAccess": [
60 | {
61 | "id": "5b567255-7703-4780-807c-7be8301ae99b",
62 | "type": "Role"
63 | },
64 | {
65 | "id": "498476ce-e0fe-48b0-b801-37ba7e2685c6",
66 | "type": "Role"
67 | },
68 | {
69 | "id": "df021288-bdef-4463-88db-98f22de89214",
70 | "type": "Role"
71 | },
72 | {
73 | "id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c",
74 | "type": "Role"
75 | },
76 | {
77 | "id": "243333ab-4d21-40cb-a475-36241daa0842",
78 | "type": "Role"
79 | },
80 | {
81 | "id": "98830695-27a2-44f7-8c18-0c3ebc9698f6",
82 | "type": "Role"
83 | }
84 | ]
85 | }
86 | ' | ConvertFrom-Json
87 |
88 | Set-AzureADApplication -ObjectId $AADApp_obj.ObjectId -RequiredResourceAccess $Permissions
89 | Write-Warning "Permission set, please open the app in AzureAD and provide a admin consent"
90 | Write-Output "App URL: https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/$($AADApp_obj.AppId)"
91 |
92 | }elseif($Force){
93 | Write-Verbose "A App with the Name $AppName aready exists. A new key will be createt"
94 | }else{
95 | Write-Warning "A App with the Name $AppName aready exists. Use -Force to create new key"
96 | break
97 | }
98 |
99 |
100 |
101 | $AADApp_creds = New-AzureADApplicationPasswordCredential -CustomKeyIdentifier PrimarySecret -ObjectId $AADApp_obj.ObjectId -EndDate ((Get-Date).AddYears(2))
102 | # Creat Connection Infos
103 | $AADApp_connection = New-Object psobject -Property @{
104 | TenantId = $AADConnection.TenantDomain;
105 | ClientId = $AADApp_obj.AppId;
106 | ClientSecret = $AADApp_creds.Value
107 | }
108 |
109 | if($Save){
110 | Write-Verbose "Save Credential object"
111 | if(!$Path){$Path = "$env:LocalAppData\IntuneDeviceInventory\AppConnection\$($AADConnection.TenantDomain).connection"}
112 | Save-IDIAppConnection -TenantId $AADApp_connection.TenantId -ClientId $AADApp_connection.ClientId -ClientSecret $AADApp_connection.ClientSecret -Path $Path
113 | }
114 | Write-Verbose "Those are your credential details, please save them."
115 | Write-Verbose $AADApp_connection
116 | return $AADApp_connection
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceDefenderScan.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceDefenderScan {
2 |
3 | <#
4 | .SYNOPSIS
5 | Trigger Defender Scan for Intune device(s)
6 |
7 | .DESCRIPTION
8 | Trigger Defender Scan for Intune device(s)
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to trigger a Defender Scan
12 |
13 | .PARAMETER Group
14 | Trigger Defender Scan for Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger Defender Scan for Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger Defender Scan for Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger Defender Scan for Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger Defender Scan for Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger Defender Scan for all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices to trigger a Defender Scan out of a GridView
33 |
34 | #>
35 |
36 | param (
37 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger a Defender Scan")]
38 | [ValidateNotNullOrEmpty()]
39 | [array]$IDIDevice,
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for Intune Devices within a specific group")]
42 | [ValidateNotNullOrEmpty()]
43 | [string]$Group,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for Intune Devices from a specific user (UPN)")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$User,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for Intune Devices by Device Name")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$deviceName,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for Intune Devices by id")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$id,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for Intune Devices by azureADDeviceId")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$azureADDeviceId,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Scan for all Intune Devices")]
62 | [ValidateNotNullOrEmpty()]
63 | [switch]$All,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices to trigger a Defender Scan out of a GridView")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$Grid
68 |
69 | )
70 |
71 |
72 | if($All){
73 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}}
74 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}}
75 |
76 | }elseif($Group){
77 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
78 |
79 | }elseif($User){
80 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
81 |
82 | }elseif($deviceName){
83 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
84 |
85 | }elseif($id){
86 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
87 |
88 | }elseif($azureADDeviceId){
89 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
90 |
91 | }elseif($Grid){
92 | if($global:IDIDevices_all){
93 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
94 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}
95 | }
96 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceDefenderScan -IDIDevice $_}}
97 |
98 | }elseif($IDIDevice){
99 | Write-Verbose "Trigger Defender Scan for device with id: $($IDIDevice.id) ..."
100 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/windowsDefenderScan"
101 | Invoke-MgGraphRequest -Uri $uri -Method POST
102 | }else{
103 | Write-Warning "No device or scope for Invoke-IDIDeviceDefenderScan specified."
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Invoke-IDIDeviceDefenderSignatures.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-IDIDeviceDefenderSignatures {
2 |
3 | <#
4 | .SYNOPSIS
5 | Trigger Defender Signatures update for Intune device
6 |
7 | .DESCRIPTION
8 | Trigger Defender Signatures update for Intune device
9 |
10 | .PARAMETER IDIDevice
11 | Array of the device to trigger Defender Signatures update
12 |
13 | .PARAMETER Group
14 | Trigger Defender Signatures update for Intune Devices within a specific group
15 |
16 | .PARAMETER User
17 | Trigger Defender Signatures update for Intune Devices from a specific user (UPN)
18 |
19 | .PARAMETER deviceName
20 | Trigger Defender Signatures update for Intune Devices by name
21 |
22 | .PARAMETER id
23 | Trigger Defender Signatures update for Intune Devices by id (deviceID)
24 |
25 | .PARAMETER azureADDeviceId
26 | Trigger Defender Signatures update for Intune Device by azureADDeviceId
27 |
28 | .PARAMETER All
29 | Trigger Defender Signatures update for all Intune Devices
30 |
31 | .PARAMETER Grid
32 | Switch to select Intune Devices to trigger Defender Signatures update out of a GridView
33 |
34 | #>
35 |
36 | param (
37 | [parameter(Mandatory = $false, HelpMessage = "Array of the device to trigger an restart")]
38 | [ValidateNotNullOrEmpty()]
39 | [array]$IDIDevice,
40 |
41 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for Intune Devices within a specific group")]
42 | [ValidateNotNullOrEmpty()]
43 | [string]$Group,
44 |
45 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for Intune Devices from a specific user (UPN)")]
46 | [ValidateNotNullOrEmpty()]
47 | [string]$User,
48 |
49 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for Intune Devices by Device Name")]
50 | [ValidateNotNullOrEmpty()]
51 | [string]$deviceName,
52 |
53 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for Intune Devices by id")]
54 | [ValidateNotNullOrEmpty()]
55 | [string]$id,
56 |
57 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for Intune Devices by azureADDeviceId")]
58 | [ValidateNotNullOrEmpty()]
59 | [string]$azureADDeviceId,
60 |
61 | [parameter(Mandatory = $false, HelpMessage = "Trigger Defender Signatures update for all Intune Devices")]
62 | [ValidateNotNullOrEmpty()]
63 | [switch]$All,
64 |
65 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices to trigger Defender Signatures update out of a GridView")]
66 | [ValidateNotNullOrEmpty()]
67 | [switch]$Grid
68 |
69 | )
70 |
71 |
72 | if($All){
73 | if($global:IDIDevices_all){$global:IDIDevices_all | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}}
74 | else{Get-noneIDIDevice -All | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}}
75 |
76 | }elseif($Group){
77 | Get-noneIDIDevice -Group "$Group" | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
78 |
79 | }elseif($User){
80 | Get-noneIDIDevice -User "$User" | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
81 |
82 | }elseif($deviceName){
83 | Get-noneIDIDevice -deviceName "$deviceName" | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
84 |
85 | }elseif($id){
86 | Get-noneIDIDevice -id "$id" | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
87 |
88 | }elseif($azureADDeviceId){
89 | Get-noneIDIDevice -azureADDeviceId "$azureADDeviceId" | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
90 |
91 | }elseif($Grid){
92 | if($global:IDIDevices_all){
93 | $Devices2Sync = $global:IDIDevices_all | Out-GridView -Title "Please select your devices" -OutputMode Multiple
94 | $Devices2Sync | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}
95 | }
96 | else{Get-noneIDIDevice -Grid | ForEach-Object{ Invoke-IDIDeviceDefenderSignatures -IDIDevice $_}}
97 |
98 | }elseif($IDIDevice){
99 | Write-Verbose "Trigger Defender Signatures update for device with id: $($IDIDevice.id) ..."
100 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$($IDIDevice.id)')/microsoft.graph.windowsDefenderUpdateSignatures"
101 | Invoke-MgGraphRequest -Uri $uri -Method POST
102 | }else{
103 | Write-Warning "No device or scope for Invoke-IDIDeviceDefenderSignatures specified."
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Get-noneIDIDevice.ps1:
--------------------------------------------------------------------------------
1 | function Get-noneIDIDevice{
2 | <#
3 | .SYNOPSIS
4 | Get Intune Device(s)
5 |
6 | .DESCRIPTION
7 | Get Intune Device(s)
8 |
9 | .PARAMETER ScopeTag
10 | Get Intune Devices with specific scope tag
11 |
12 | .PARAMETER Group
13 | Get Intune Devices within a specific group
14 |
15 | .PARAMETER User
16 | Get Intune Devices from a specific user (UPN)
17 |
18 | .PARAMETER deviceName
19 | Get Intune Devices by name
20 |
21 | .PARAMETER id
22 | Get Intune Devices by id (deviceID)
23 |
24 | .PARAMETER azureADDeviceId
25 | Get Intune Device by azureADDeviceId
26 |
27 | .PARAMETER All
28 | Get all Intune Devices
29 |
30 | .PARAMETER Grid
31 | Switch to select Intune Devices out of a GridView
32 |
33 | #>
34 |
35 | param (
36 | #[parameter(Mandatory = $false, HelpMessage = "Get Intune Devices with specific scope tag")]
37 | #[ValidateNotNullOrEmpty()]
38 | #[string]$ScopeTag,
39 |
40 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices within a specific group")]
41 | [ValidateNotNullOrEmpty()]
42 | [string]$Group,
43 |
44 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices from a specific primary user (UPN)")]
45 | [ValidateNotNullOrEmpty()]
46 | [string]$User,
47 |
48 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by Device Name")]
49 | [ValidateNotNullOrEmpty()]
50 | [string]$deviceName,
51 |
52 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by id")]
53 | [ValidateNotNullOrEmpty()]
54 | [string]$id,
55 |
56 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by azureADDeviceId")]
57 | [ValidateNotNullOrEmpty()]
58 | [string]$azureADDeviceId,
59 |
60 | [parameter(Mandatory = $false, HelpMessage = "Get all Intune Devices")]
61 | [ValidateNotNullOrEmpty()]
62 | [switch]$All,
63 |
64 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
65 | [ValidateNotNullOrEmpty()]
66 | [switch]$Grid
67 |
68 | )
69 |
70 | # Set script variable for error action preference
71 | $ErrorActionPreference = "Stop"
72 |
73 | # Colection Variable
74 | $IntuneDevices = @()
75 |
76 | if($All){
77 | Write-Verbose "Get all managed Devices from Intune..."
78 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices"
79 | $Method = "GET"
80 | $IntuneDevices = (Invoke-PagingRequest -URI $uri -Method $Method)
81 |
82 | }elseif($ScopeTag){
83 | # tbd
84 | Write-Warning "Parameter -ScopeTag is in the making, please use anotherone in the meantime"
85 |
86 | }elseif($Group){
87 | Write-Verbose "Get group ID for $Group ..."
88 | $uri = "https://graph.microsoft.com/beta/groups?`$filter=displayName%20eq%20'$Group'"
89 | $GroupID = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value.id
90 |
91 | Write-Verbose "Get group members for ID: $GroupID ..."
92 | $uri = "https://graph.microsoft.com/beta/groups('$GroupID')/transitiveMembers"
93 | $Method = "GET"
94 | $GroupMembersID = (Invoke-PagingRequest -URI $uri -Method $Method).deviceId
95 |
96 | Write-Verbose "Call function with -azureADDeviceId ($($GroupMembersID.count) members found)..."
97 | foreach($AADDID in $GroupMembersID){
98 | if($AADDID){ $IntuneDevices += Get-noneIDIDevice -azureADDeviceId $AADDID }
99 | }
100 |
101 | }elseif($User){
102 | Write-Verbose "Get managed Intune Devices where user is: $User ..."
103 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=userPrincipalName%20eq%20'$User'"
104 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
105 | }elseif($deviceName){
106 | Write-Verbose "Get managed Device from Intune with name: $deviceName ..."
107 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=deviceName%20eq%20'$deviceName'"
108 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
109 |
110 | }elseif($id){
111 | Write-Verbose "Get managed Device from Intune by id: $id ..."
112 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$id')"
113 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop)
114 |
115 | }elseif($azureADDeviceId){
116 | Write-Verbose "Get managed Device from Intune by azureADDeviceId: $azureADDeviceId ..."
117 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=azureADDeviceId%20eq%20'$azureADDeviceId'"
118 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
119 |
120 | }elseif($Grid){
121 | $IntuneDevices = Get-noneIDIDevice -All | Out-GridView -Title "Please select your devices" -OutputMode Multiple
122 | }else{
123 | Write-Warning "No Parameter specified for Get-noneIDIDevice"
124 | }
125 |
126 | if(!$IntuneDevices){Write-Warning "No device was found with the specified search criteria."}
127 | return $IntuneDevices
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/UI/Start-IntuneDeviceInventoryUi.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | Version: 1.0
3 | Author: Florian Salzman (scloud.work) / Jannik Reinhard (jannikreinhard.com)
4 | Script: Start-IntuneDeviceInventoryUi
5 | Description:
6 | Start and init the IntuneDeviceInventoryUi
7 | Release notes:
8 | 1.0 :
9 | - Init
10 | #>
11 | ###########################################################################################################
12 | ############################################ Functions ####################################################
13 | ###########################################################################################################
14 | function Get-MessageScreen {
15 | param (
16 | [Parameter(Mandatory = $true)]
17 | [String]$xamlPath
18 | )
19 |
20 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
21 | Add-Type -AssemblyName PresentationFramework
22 | [xml]$xaml = Get-Content $xamlPath
23 | $global:messageScreen = ([Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml)))
24 | [System.Windows.Forms.Application]::DoEvents()
25 | }
26 |
27 | function Get-LoadingMessageScreen {
28 | param (
29 | [Parameter(Mandatory = $true)]
30 | [String]$xamlPath
31 | )
32 |
33 | Get-MessageScreen -xamlPath $xamlPath
34 | $global:messageScreenTitle = $global:messageScreen.FindName("TextMessageHeader")
35 | $global:messageScreenText = $global:messageScreen.FindName("TextMessageBody")
36 | $global:button1 = $global:messageScreen.FindName("ButtonMessage1")
37 | $global:button2 = $global:messageScreen.FindName("ButtonMessage2")
38 |
39 | $global:messageScreenTitle.Text = "Initializing Device Troubleshooter"
40 | $global:messageScreenText.Text = "Starting Device Troubleshooter"
41 | [System.Windows.Forms.Application]::DoEvents()
42 | $global:messageScreen.Show() | Out-Null
43 | [System.Windows.Forms.Application]::DoEvents()
44 | }
45 |
46 | function Import-AllModules {
47 | foreach ($file in (Get-Item -path "$global:Path\modules\*.psm1")) {
48 | $fileName = [IO.Path]::GetFileName($file)
49 | if ($skipModules -contains $fileName) { Write-Warning "Module $fileName excluded"; continue; }
50 |
51 | $module = Import-Module $file -PassThru -Force -Global -ErrorAction SilentlyContinue
52 | if ($module) {
53 | Set-MessageScreenText -text "Module $($module.Name) loaded successfully"
54 | }
55 | else {
56 | Set-MessageScreenText -text "Failed to load module $file"
57 | return $false
58 | }
59 | }
60 | return $true
61 | }
62 |
63 | function Set-MessageScreenText {
64 | param (
65 | [Parameter(Mandatory = $true)]
66 | [String]$text,
67 | [String]$header
68 | )
69 |
70 | if ($header) { $global:messageScreenTitle.Text = $header }
71 | $global:messageScreenText.Text = $text
72 | [System.Windows.Forms.Application]::DoEvents()
73 | }
74 |
75 | function Exit-Error {
76 | param (
77 | [Parameter(Mandatory = $true)]
78 | [String]$text
79 | )
80 |
81 | Write-Error $text
82 | $global:messageScreen.Hide()
83 | $global:formAuth.Hide()
84 | Exit
85 | }
86 |
87 | ###########################################################################################################
88 | ############################################## Start ######################################################
89 | ###########################################################################################################
90 | ### Variables
91 | # General
92 | $global:ToolName = "Intune Device Inventory UI"
93 | $global:Version = "0.1"
94 | $global:Developer = "Florian Salzmann and Jannik Reinhard"
95 | $global:Path = $PSScriptRoot
96 | $global:AuthMethod = $null
97 | $global:AuthTenant = $null
98 | $global:AuthAppId = $null
99 | $global:AuthAppSecret = $null
100 |
101 | # Start Start Screen
102 | Get-LoadingMessageScreen -xamlPath ("$global:Path\xaml\message.xaml")
103 | Set-MessageScreenText -text "Starting $toolName" -header "Initializing $toolName"
104 |
105 | # Load custom modules
106 | Set-MessageScreenText -text "Load all required modules"
107 | if (-not (Import-AllModules)) { Exit-Error -text "Error while loading the modules" }
108 |
109 | # Load Dlls
110 | Set-MessageScreenText -text "Load all required DLLs"
111 | if (-not (Import-Dlls)) {
112 | Write-Warning "Unblock all dlls and restart the powershell seassion"
113 | Exit-Error -text "Error while loading the dlls. Exit the script"
114 | }
115 |
116 | # Create Temp folder
117 | # Set-MessageScreenText -text "Create temp folder if not exist"
118 | # $folder = Add-TempFolder
119 |
120 | # Install Idi odule
121 | Set-MessageScreenText -text "Try to install IntuneDeviceInventory module"
122 | if (-not (Install-IdiModule)) { Exit-Error -text "Error while install the IntuneDeviceInventory modules" }
123 |
124 | # Authenticate
125 | Set-MessageScreenText -text "Try to authenticate on Graph"
126 | Get-Authenticated
127 | Get-LoadingMessageScreen -xamlPath ("$global:Path\xaml\message.xaml")
128 |
129 | # Init Ui
130 | Set-MessageScreenText -text "Init User Interface"
131 | try{
132 | $returnMainForm = New-XamlScreen -xamlPath ("$global:Path\xaml\ui.xaml")
133 | $global:formMainForm = $returnMainForm[0]
134 | $xamlMainForm = $returnMainForm[1]
135 | $xamlMainForm.SelectNodes("//*[@Name]") | % {Set-Variable -Name "WPF$($_.Name)" -Value $formMainForm.FindName($_.Name)}
136 | $global:formMainForm.add_Loaded({
137 | $global:messageScreen.Hide()
138 | $global:formMainForm.Activate()
139 | })
140 | }catch{
141 | Exit-Error -text "Failed to init UI"
142 | }
143 | if (-not (New-UiInti)) { Exit-Error -text "Failed to init UI" }
144 |
145 | # Get all devices
146 | Set-MessageScreenText -text "Get all devices"
147 | $global:allDevices = Get-AllDevices
148 | if ($global:allDevices -eq $false) { Exit-Error -text "Failed to get all devices" }
149 |
150 | # Add Devices
151 | Set-MessageScreenText -text "Add devices to grid"
152 | $global:allDevicesGrid = Add-DevicesToGridObject -devices $global:allDevices
153 | Add-DevicesToGrid -devices $global:allDevicesGrid
154 |
155 | $global:messageScreen.Hide()
156 | $global:formMainForm.ShowDialog() | out-null
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/Public/Get-IDIDevice.ps1:
--------------------------------------------------------------------------------
1 | function Get-IDIDevice{
2 | <#
3 | .SYNOPSIS
4 | Get Intune Device and compile notes (json) into the output
5 |
6 | .DESCRIPTION
7 | Get Intune Device and compile notes (json) into the output
8 |
9 | .PARAMETER ScopeTag
10 | Get Intune Devices with specific scope tag
11 |
12 | .PARAMETER Group
13 | Get Intune Devices within a specific group
14 |
15 | .PARAMETER User
16 | Get Intune Devices from a specific user (UPN)
17 |
18 | .PARAMETER deviceName
19 | Get Intune Devices by name
20 |
21 | .PARAMETER id
22 | Get Intune Devices by id (deviceID)
23 |
24 | .PARAMETER azureADDeviceId
25 | Get Intune Device by azureADDeviceId
26 |
27 | .PARAMETER All
28 | Get all Intune Devices
29 |
30 | .PARAMETER Grid
31 | Switch to select Intune Devices out of a GridView
32 |
33 | .PARAMETER Progress
34 | Switch to show progress for loading notes
35 |
36 | #>
37 |
38 | param (
39 | #[parameter(Mandatory = $false, HelpMessage = "Get Intune Devices with specific scope tag")]
40 | #[ValidateNotNullOrEmpty()]
41 | #[string]$ScopeTag,
42 |
43 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices within a specific group")]
44 | [ValidateNotNullOrEmpty()]
45 | [string]$Group,
46 |
47 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices from a specific primary user (UPN)")]
48 | [ValidateNotNullOrEmpty()]
49 | [string]$User,
50 |
51 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by Device Name")]
52 | [ValidateNotNullOrEmpty()]
53 | [string]$deviceName,
54 |
55 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by id")]
56 | [ValidateNotNullOrEmpty()]
57 | [string]$id,
58 |
59 | [parameter(Mandatory = $false, HelpMessage = "Get Intune Devices by azureADDeviceId")]
60 | [ValidateNotNullOrEmpty()]
61 | [string]$azureADDeviceId,
62 |
63 | [parameter(Mandatory = $false, HelpMessage = "Get all Intune Devices")]
64 | [ValidateNotNullOrEmpty()]
65 | [switch]$All,
66 |
67 | [parameter(Mandatory = $false, HelpMessage = "Switch to select Intune Devices out of a GridView")]
68 | [ValidateNotNullOrEmpty()]
69 | [switch]$Grid,
70 |
71 | [parameter(Mandatory = $false, HelpMessage = "Switch to show progress for loading notes")]
72 | [ValidateNotNullOrEmpty()]
73 | [switch]$Progress
74 |
75 | )
76 |
77 | # Set script variable for error action preference
78 | $ErrorActionPreference = "Stop"
79 |
80 | # Colection Variable
81 | $SelectedIDIDevices = @()
82 |
83 | if($All){
84 | Write-Verbose "Get all managed Devices from Intune..."
85 | if($Progress){Write-Output "Reading all managed devices, progress starts shortly..."}
86 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices"
87 | $Method = "GET"
88 | $IntuneDevices = (Invoke-PagingRequest -URI $uri -Method $Method)
89 |
90 | $global:IDIDevices_all = $IntuneDevices
91 | Write-Verbose "All devices ($($global:IDIDevices_all.count)) saved in `$global:IDIDevices_all"
92 |
93 | }elseif($ScopeTag){
94 | # tbd
95 | Write-Warning "Parameter -ScopeTag is in the making, please use anotherone in the meantime"
96 |
97 | }elseif($Group){
98 | Write-Verbose "Get group ID for $Group ..."
99 | $uri = "https://graph.microsoft.com/beta/groups?`$filter=displayName%20eq%20'$Group'"
100 | $GroupID = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value.id
101 |
102 | Write-Verbose "Get group members for ID: $GroupID ..."
103 | $uri = "https://graph.microsoft.com/beta/groups('$GroupID')/transitiveMembers"
104 | $Method = "GET"
105 | $GroupMembersID = (Invoke-PagingRequest -URI $uri -Method $Method).deviceId
106 |
107 | Write-Verbose "Call function with -azureADDeviceId ($($GroupMembersID.count) members found)..."
108 | foreach($AADDID in $GroupMembersID){
109 | if($AADDID){ $IntuneDevices += Get-IDIDevice -azureADDeviceId $AADDID }
110 | }
111 |
112 | }elseif($User){
113 | Write-Verbose "Get managed Intune Devices where user is: $User ..."
114 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=userPrincipalName%20eq%20'$User'"
115 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
116 | }elseif($deviceName){
117 | Write-Verbose "Get managed Device from Intune with name: $deviceName ..."
118 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=deviceName%20eq%20'$deviceName'"
119 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
120 |
121 | }elseif($id){
122 | Write-Verbose "Get managed Device from Intune by id: $id ..."
123 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$id')"
124 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop)
125 |
126 | }elseif($azureADDeviceId){
127 | Write-Verbose "Get managed Device from Intune by azureADDeviceId: $azureADDeviceId ..."
128 | $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=azureADDeviceId%20eq%20'$azureADDeviceId'"
129 | $IntuneDevices = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
130 |
131 | }elseif($Grid)
132 | {
133 | $SelectedIDIDevices = Get-IDIDevice -All | Out-GridView -Title "Please select your devices" -OutputMode Multiple
134 | return $SelectedIDIDevices
135 | break
136 |
137 | }else{
138 | Write-Warning "No Parameter specified for Get-IDIDevice"
139 | }
140 |
141 | if(!$IntuneDevices){
142 | Write-Warning "No device was found with the specified search criteria."
143 | }else{
144 | Write-Verbose "Get notes for selected managed Device from Intune ..."
145 | if($Progress){
146 | # Variables for write Progress
147 | $TotalItems = $IntuneDevices.Count
148 | $CurrentItem = 0
149 | $PercentComplete = 0
150 | }
151 | if($IntuneDevices.count -gt 1){
152 | foreach($IntuneDevice in $IntuneDevices){
153 | if($Progress){
154 | Write-Progress -Activity "Reading notes from $TotalItems devices..." -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
155 | $CurrentItem++
156 | $PercentComplete = [int](($CurrentItem / $TotalItems) * 100)
157 | }
158 | $SelectedIDIDevices += Get-IDIDeviceNotes -IDIDevice $IntuneDevice
159 | }
160 | }else{ $SelectedIDIDevices = Get-IDIDeviceNotes -IDIDevice $IntuneDevices[0] }
161 |
162 | return $SelectedIDIDevices
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/UI/modules/authHandler.psm1:
--------------------------------------------------------------------------------
1 | <#
2 | Version: 1.0
3 | Author: Florian Salzman (scloud.work) / Jannik Reinhard (jannikreinhard.com)
4 | Script: authHandler
5 | Description:
6 | Handel the authentication for the intune devie inventory
7 | Release notes:
8 | 1.0 :
9 | - Init
10 | #>
11 |
12 |
13 | function Get-Authenticated {
14 | Open-AuthSelectionWindows
15 | if($global:AuthMethod -eq "User") {
16 | if (-not (Get-IDIAuthenticatedUser)) { Exit-Error -text "Failed to authenticate" }
17 | }elseif($global:AuthMethod -eq "Manual"){
18 | Open-AuthServicePrincipleDetails -Manual $true
19 | if (-not (Get-IDIAuthenticatedServicePrinciple -tenantId $global:AuthTenant -clientId $global:AuthAppId -clientSecret $global:AuthAppSecret)) { Exit-Error -text "Failed to authenticate" }
20 | }elseif($global:AuthMethod -eq "Auto"){
21 | if(test-path "$env:LocalAppData\IntuneDeviceInventory\AppConnection\*.connection" -pathtype leaf){
22 | Open-AuthServicePrincipleDetails -ConnectionSelection $true
23 | }else{
24 | $global:AuthTenant = (New-IDIApp -Save).TenantId
25 | Approve-ServicePrinciple
26 | }
27 | if (-not (Get-IDIAuthenticatedServicePrincipleAuto -tenantId $global:AuthTenant)) { Exit-Error -text "Failed to authenticate" }
28 | }
29 | }
30 |
31 | function Get-IDIAuthenticatedUser{
32 | try {
33 | Connect-IDI
34 | }
35 | catch {
36 | Write-Error "Failed to authenticate on Graph: $_"
37 | return $false
38 | }
39 | return $true
40 | }
41 |
42 | function Get-IDIAuthenticatedServicePrinciple{
43 | param (
44 | [parameter(Mandatory = $false)] [string]$tenantId,
45 | [parameter(Mandatory = $false)] [string]$clientId,
46 | [parameter(Mandatory = $false)] [string]$clientSecret
47 | )
48 |
49 | try {
50 | Connect-IDI -ClientId $clientId -TenantId $tenantId -ClientSecret $clientSecret
51 | }
52 | catch {
53 | Write-Error "Failed to authenticate on Graph: $_"
54 | return $false
55 | }
56 | return $true
57 | }
58 |
59 | function Get-IDIAuthenticatedServicePrincipleAuto{
60 | param (
61 | [parameter(Mandatory = $false)] [string]$tenantId
62 | )
63 | try {
64 | Import-IDIAppConnection -TenantId $tenantId -Connect $true
65 | }
66 | catch {
67 | Write-Error "Failed to authenticate on Graph: $_"
68 | return $false
69 | }
70 | return $true
71 | }
72 |
73 |
74 | function Open-AuthSelectionWindows {
75 | try{
76 | $returnMainForm = New-XamlScreen -xamlPath ("$global:Path\xaml\authSelection.xaml")
77 | $global:formAuth = $returnMainForm[0]
78 | $xamlFormAuth = $returnMainForm[1]
79 | $xamlFormAuth.SelectNodes("//*[@Name]") | % {Set-Variable -Name "WPFA$($_.Name)" -Value $formAuth.FindName($_.Name) -scope global}
80 | $global:formAuth.add_Loaded({
81 | $global:messageScreen.Hide()
82 | $global:formAuth.Activate()
83 | })
84 | }catch{
85 | Exit-Error -text "Failed to init auth selection UI"
86 | }
87 |
88 | Add-XamlEvent -object $global:WPFAButtonMessage1 -event "Add_Click" -scriptBlock {
89 | if($global:WPFARbUser.IsChecked ){$global:AuthMethod = "User"}
90 | if($global:WPFARbManual.IsChecked ){$global:AuthMethod = "Manual"}
91 | if($global:WPFARbAuto.IsChecked ){$global:AuthMethod = "Auto"}
92 | $global:formAuth.Hide()
93 | }
94 | $global:formAuth.ShowDialog() | out-null
95 | }
96 |
97 |
98 | function Open-AuthServicePrincipleDetails {
99 | param (
100 | [parameter(Mandatory = $false)] [bool]$manual,
101 | [parameter(Mandatory = $false)] [bool]$connectionSelection
102 | )
103 |
104 | try{
105 | $returnMainForm = New-XamlScreen -xamlPath ("$global:Path\xaml\authServicePrinciple.xaml")
106 | $global:formAuth = $returnMainForm[0]
107 | $xamlFormAuth = $returnMainForm[1]
108 | $xamlFormAuth.SelectNodes("//*[@Name]") | % {Set-Variable -Name "WPFAS$($_.Name)" -Value $formAuth.FindName($_.Name) -scope global}
109 | if($manual){
110 | $global:WPFASTxtTenantId.Visibility = "Visible"
111 | $global:WPFASTxtAppId.Visibility = "Visible"
112 | $global:WPFASTxtSecret.Visibility = "Visible"
113 | $global:WPFASLblTenantId.Visibility = "Visible"
114 | $global:WPFASLblAppId.Visibility = "Visible"
115 | $global:WPFASLblAppSecret.Visibility = "Visible"
116 |
117 | $global:WPFASLblSecret.Visibility = "Collapsed"
118 | $global:WPFASComboboxConnection.Visibility = "Collapsed"
119 | }elseif($connectionSelection){
120 | $global:WPFASTxtTenantId.Visibility = "Collapsed"
121 | $global:WPFASTxtAppId.Visibility = "Collapsed"
122 | $global:WPFASTxtSecret.Visibility = "Collapsed"
123 | $global:WPFASLblTenantId.Visibility = "Collapsed"
124 | $global:WPFASLblAppId.Visibility = "Collapsed"
125 | $global:WPFASLblAppSecret.Visibility = "Collapsed"
126 |
127 | $global:WPFASLblSecret.Visibility = "Visible"
128 | $global:WPFASComboboxConnection.Visibility = "Visible"
129 |
130 | $connections = Get-ChildItem -Path "$env:LocalAppData\IntuneDeviceInventory\AppConnection\*.connection" -Force
131 | foreach ($connection in $connections) { $global:WPFASComboboxConnection.items.Add($connection.Name) | Out-Null }
132 | $WPFASComboboxConnection.SelectedIndex = 0
133 | }else{
134 | $global:WPFASTxtTenantId.Visibility = "Visible"
135 | $global:WPFASTxtAppId.Visibility = "Collapsed"
136 | $global:WPFASTxtSecret.Visibility = "Collapsed"
137 | $global:WPFASLblTenantId.Visibility = "Visible"
138 | $global:WPFASLblAppId.Visibility = "Collapsed"
139 | $global:WPFASLblAppSecret.Visibility = "Collapsed"
140 |
141 | $global:WPFASLblSecret.Visibility = "Collapsed"
142 | $global:WPFASComboboxConnection.Visibility = "Collapsed"
143 | }
144 |
145 | $global:formAuth.add_Loaded({
146 | $global:formAuth.Activate()
147 | })
148 | }catch{
149 | Exit-Error -text "Failed to init auth UI"
150 | }
151 |
152 | Add-XamlEvent -object $global:WPFASButtonMessage1 -event "Add_Click" -scriptBlock {
153 | if(-not $global:WPFASTxtTenantId.Text -and -not $connectionSelection){
154 | [System.Windows.MessageBox]::Show('Insert the TenantId')
155 | return
156 | }
157 |
158 | if($Manual -and -not $global:WPFASTxtAppId.Text -and -not $connectionSelection){
159 | [System.Windows.MessageBox]::Show("Insert the AppId")
160 | return
161 | }
162 | if($Manual -and -not $global:WPFASTxtSecret.Text -and -not $connectionSelection){
163 | [System.Windows.MessageBox]::Show("Insert the Secret")
164 | return
165 | }
166 | $global:AuthTenant = $global:WPFASTxtTenantId
167 | $global:AuthAppId = $global:WPFASTxtAppId
168 | $global:AuthAppSecret = $global:WPFASTxtSecret
169 | if($connectionSelection){
170 | if($global:WPFASComboboxConnection.SelectedItem){
171 | $global:AuthTenant = ($global:WPFASComboboxConnection.SelectedItem).Replace('.connection','')
172 | }else{
173 | Exit-Error -text "Erro while getting selected connection"
174 | }
175 | }
176 | $global:formAuth.Hide()
177 | }
178 | $global:formAuth.ShowDialog() | out-null
179 | }
180 |
181 |
182 | function Approve-ServicePrinciple {
183 | try{
184 | $returnMainForm = New-XamlScreen -xamlPath ("$global:Path\xaml\authServicePrinciple.xaml")
185 | $global:formAuth = $returnMainForm[0]
186 | $xamlFormAuth = $returnMainForm[1]
187 | $xamlFormAuth.SelectNodes("//*[@Name]") | % {Set-Variable -Name "WPFAS1$($_.Name)" -Value $formAuth.FindName($_.Name) -scope global}
188 | $global:WPFAS1TxtTenantId.Visibility = "Collapsed"
189 | $global:WPFAS1LblTenantId.Visibility = "Collapsed"
190 | $global:WPFAS1TextMessageBody.Text = "Approve the Service Principle Consent, wait 1 min and click Next"
191 |
192 | $global:formAuth.add_Loaded({
193 | $global:formAuth.Activate()
194 | })
195 | }catch{
196 | Exit-Error -text "Failed to init UI"
197 | }
198 |
199 | Add-XamlEvent -object $global:WPFAS1ButtonMessage1 -event "Add_Click" -scriptBlock {
200 | $global:formAuth.Hide()
201 | }
202 |
203 | $global:formAuth.ShowDialog() | out-null
204 | }
205 |
206 |
--------------------------------------------------------------------------------
/Module/IntuneDeviceInventory/readme.md:
--------------------------------------------------------------------------------
1 | |Florian Salzmann|[](https://twitter.com/FlorianSLZ/) [](https://www.linkedin.com/in/fsalzmann/) [](https://scloud.work/en/about)|
2 | |----------------|-------------------------------|
3 | |**Jannik Reinhard**|[](https://twitter.com/jannik_reinhard) [](https://www.linkedin.com/in/jannik-r/) [](https://jannikreinhard.com/)|
4 |
5 | # IntuneDeviceInventory (IDI)
6 | 
7 |
8 | This module was created to have the ability to add more pieces of information to a Microsoft Intune device object.
9 | In addition, there are some functions to bulk initiate Intune commands like a Sync for devices.
10 |
11 |
12 | ## Installing the module from PSGallery
13 |
14 | The IntuneWin32App module is published to the [PowerShell Gallery](https://www.powershellgallery.com/packages/IntuneDeviceInventory). Install it on your system by running the following in an elevated PowerShell console:
15 | ```PowerShell
16 | Install-Module -Name IntuneDeviceInventory
17 | ```
18 |
19 | ## Import the module for testing
20 |
21 | As an alternative to installing, you chan download this Repository and import it in a PowerShell Session.
22 | *The path may be different in your case*
23 | ```PowerShell
24 | Import-Module -Name "C:\GitHub\IntuneDeviceInventory\Module\IntuneDeviceInventory" -Verbose -Force
25 | ```
26 |
27 | ## Module dependencies
28 |
29 | IntuneDeviceInventory module requires the following modules, which will be automatically installed as dependencies:
30 | - Microsoft.Graph.Intune
31 |
32 | # Functions / Examples
33 |
34 | Here are all functions and some examples to start with:
35 |
36 | - Add-IDIProperty
37 | - Backup-IDI
38 | - Connect-IDI
39 | - ConvertTo-IDINotes
40 | - Get-IDIDevice
41 | - Get-IDIDeviceNotes
42 | - Get-noneIDIDevice
43 | - Get-noneIDIReference
44 | - Import-IDIAppConnection
45 | - Invoke-IDIDeviceBitLockerRotation
46 | - Invoke-IDIDeviceDefenderScan
47 | - Invoke-IDIDeviceDefenderSignatures
48 | - Invoke-IDIDeviceDelete
49 | - Invoke-IDIDeviceRestart
50 | - Invoke-IDIDeviceRetire
51 | - Invoke-IDIDeviceSync
52 | - Invoke-IDIDeviceWipe
53 | - Invoke-PagingRequest
54 | - New-IDIApp
55 | - Remove-IDIAppConnection
56 | - Restore-IDI
57 | - Save-IDIAppConnection
58 | - Set-IDIDevice
59 | - Set-IDIDeviceNotes
60 | - Start-IDI
61 | - Test-4IDIDevices
62 |
63 | ## Authentication
64 | Before using any of the functions within this module that interacts with Graph API, ensure you are authenticated.
65 |
66 | ### User Authentication
67 | With this command, you'll be connected to the Graph API and be able to use all commands
68 | ```PowerShell
69 | # Authentication as User
70 | Connect-IDI
71 |
72 | # Authentication via Azure App
73 | Connect-IDI -ClientId $ClientId -TenantId $TenantId -ClientSecret $ClientSecret
74 |
75 | # Authentication with a saved Azure App
76 | Import-IDIAppConnection -TenantId $TenantId -Connect
77 | ## Authentication with a saved Azure App (if you have multiple and want to select)
78 | Import-IDIAppConnection -Select -Connect
79 | ```
80 |
81 | ## Basic commands
82 | ### Get Devices
83 |
84 | ```PowerShell
85 | # Get all devices with notes
86 | Get-IDIDevice -All
87 |
88 | # Get all devices without notes
89 | Get-noneIDIDevice -All
90 | ```
91 |
92 | ### Adding a Prperty
93 |
94 | ```PowerShell
95 | Add-IDIProperty -PropertyName "Monitor"
96 | ```
97 |
98 | ### Managing a Device
99 |
100 | ```PowerShell
101 | # Select the device
102 | $Device2edit = $IDIDevices_all | Out-GridView -OutputMode Single
103 |
104 | # Set Device Property
105 | $Device2edit.Monitor = 'Samsung Odyssey G9'
106 |
107 | # Update Device in Intune with changes
108 | Set-IDIDevice -IDIDevice $Device2edit
109 | ```
110 |
111 | ### Converting none IDI notes
112 |
113 | If you already have any notes filed up, you can convert them into "IDI-notes", so that they are compatible with all the commands and the custom fields.
114 | ```PowerShell
115 | # Convert notes of a single device
116 | ConvertTo-IDINotes -DeviceId 892582d8-xxxx-xxxx-xxxx-afe0ada8b8d2 -PropertyName "purchase date"
117 |
118 | # Convert all notes
119 | ## if notes from a device are already compatible, they won't be processed
120 | ConvertTo-IDINotes -All -PropertyName "purchase date"
121 | ```
122 | ## Backup & Restore
123 | If you have a lot of infos in your custom field fore sure you wat to bakup them.
124 | The Backup is stored in a JSON file.
125 | The Backup&Restore functiuonality also works in a Tenant to Teannt migration.
126 |
127 | ```PowerShell
128 | # Backup current configuration (if no path is specified, it will be stored in the users temp)
129 | Backup-IDI
130 |
131 | # Restore previosly backuped configuration (same tenant)
132 | Restore-IDI -Path "C:\...\backup.json"
133 |
134 | # Restore to a new tenant, devices will match by serialnumber
135 | Restore-IDI -Path "C:\...\backup.json" -serial
136 | ```
137 |
138 | ## Bulk commands
139 |
140 | With the bulk commands (starting with Invoke-) you chan easily perform INtune bulk actions for selected devices.
141 |
142 | ### Sync all devices
143 |
144 | ```PowerShell
145 | Invoke-IDIDeviceSync -All
146 | ```
147 |
148 | ### Reboot devices from group
149 |
150 | ```PowerShell
151 | Invoke-IDIDeviceRestart -Group "DEV-WIN-Pilot" # replace DEV-WIN-Pilot with your group name
152 | ```
153 |
154 | ### Trigger Defender Scan for selected devices (GridView)
155 |
156 | ```PowerShell
157 | Invoke-IDIDeviceDefenderScan -Grid
158 | ```
159 |
160 | ### Trigger Defender Signatures update for device name
161 |
162 | ```PowerShell
163 | Invoke-IDIDeviceDefenderSignatures -deviceName 'dev-w11-01'
164 | ```
165 |
166 | ### Trigger Wipe for device name
167 |
168 | ```PowerShell
169 | Invoke-IDIDeviceWipe -deviceName 'dev-w10-01'
170 |
171 | # or to wipe witout asking
172 | Invoke-IDIDeviceWipe -deviceName 'dev-w10-01' -Force
173 | ```
174 |
175 | ## Azure AD Applications
176 | With Azure AD Applications you have the possibility to access all features without a user login.
177 | You will need either a Global Administrator or Application Administrator to register the app in Azure.
178 |
179 | Permissions for the application:
180 | - DeviceManagementManagedDevices.PrivilegedOperations.All
181 | - DeviceManagementManagedDevices.ReadWrite.All
182 | - Group.Read.All
183 | - GroupMember.Read.All
184 | - Organization.Read.All
185 | - User.Read.All
186 |
187 | ### Creating a Azure AD Applications
188 | For creating a new App or secret you can use **New-IDIApp**. The only thing you have to do after the creation process is to give admin consent for the permissions.
189 | *The consent is only needed when creating a new app, not when adding a client secret*
190 | ```PowerShell
191 | # Creates a new App and shows connection details
192 | # login required
193 | New-IDIApp
194 |
195 | # Creates a new App and saves the details encrypted in the users AppData:
196 | # ("C:\Users\%username%\AppData\Local\IntuneDeviceInventory\AppConnection\TenantId.connection")
197 | New-IDIApp -Save
198 |
199 | # Creates a new App and saves the details encrypted in the users AppData
200 | # -Force creates a new secret if the app already exists
201 | New-IDIApp -Save -Force
202 | ```
203 |
204 | 
205 | 
206 |
207 |
208 | ### manually save Azure AD Applications details
209 | If you already have an app/secret you can save the connection details manually:
210 | ```PowerShell
211 | Save-IDIAppConnection -ClientId $ClientId -TenantId $TenantId -ClientSecret $ClientSecret
212 | ```
213 |
214 | ### Get saved App connections
215 |
216 | ```PowerShell
217 | # list all a saved Azure App connections
218 | Import-IDIAppConnection -All
219 |
220 | # Authentication with a saved Azure App
221 | Import-IDIAppConnection -TenantId $TenantId
222 |
223 | ## Authentication with a saved Azure App (if you have multiple and want to select)
224 | Import-IDIAppConnection -Select
225 | ```
--------------------------------------------------------------------------------
/UI/xaml/ui.xaml:
--------------------------------------------------------------------------------
1 |
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 | Get more information about the Intune Device Inventory Tool in the
50 | blog post
51 | or via the
52 | readme on git.
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
71 |
77 |
83 |
84 |
85 |
86 |
92 |
98 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
125 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
215 |
221 |
227 |
233 |
239 |
240 |
241 |
247 |
253 |
259 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
343 |
349 |
355 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
425 |
426 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
--------------------------------------------------------------------------------
/UI/modules/uiHandler.psm1:
--------------------------------------------------------------------------------
1 | <#
2 | Version: 1.0
3 | Author: Florian Salzman (scloud.work) / Jannik Reinhard (jannikreinhard.com)
4 | Script: deviceInventoryHandler
5 | Description:
6 | Handel the ui actions
7 | Release notes:
8 | 1.0 :
9 | - Init
10 | #>
11 |
12 | ########################################################################################
13 | ###################################### UI Actions ######################################
14 | ########################################################################################
15 | function Hide-All {
16 | $WPFTextboxSearchBoxDevice.Text = ""
17 | $WPFGridAbout.Visibility = "Collapsed"
18 | $WPFGridDeviceFinder.Visibility = "Collapsed"
19 | $WPFGridShowDevice.Visibility = "Collapsed"
20 | $WPFGridShowDevicesMulti.Visibility = "Collapsed"
21 | }
22 |
23 | function Set-UiActionButton {
24 | #Home
25 | Add-XamlEvent -object $WPFButtonHome -event "Add_Click" -scriptBlock {
26 | Hide-All
27 | $WPFGridDeviceFinder.Visibility = "Visible"
28 | }
29 |
30 | #About
31 | Add-XamlEvent -object $WPFButtonAbout -event "Add_Click" -scriptBlock {
32 | if ($WPFGridAbout.Visibility -eq "Visible") {
33 | Hide-All
34 | $WPFGridDeviceFinder.Visibility = "Visible"
35 | }
36 | else {
37 | Hide-All
38 | $WPFGridAbout.Visibility = "Visible"
39 | }
40 | }
41 | Add-XamlEvent -object $WPFBlogPost -event "Add_Click" -scriptBlock { Start-Process "https://github.com/JayRHa/Intune-Device-Troubleshooter" }
42 | Add-XamlEvent -object $WPFReadme -event "Add_Click" -scriptBlock { Start-Process "https://github.com/FlorianSLZ/IntuneDeviceInventory/blob/main/readme.md" }
43 |
44 | Add-XamlEvent -object $WPFButtonAboutWordpressFs -event "Add_Click" -scriptBlock { Start-Process "https://www.linkedin.com/in/fsalzmann/" }
45 | Add-XamlEvent -object $WPFButtonAboutTwitterFs -event "Add_Click" -scriptBlock { Start-Process "https://twitter.com/FlorianSLZ/" }
46 | Add-XamlEvent -object $WPFButtonAboutLinkedInFs -event "Add_Click" -scriptBlock { Start-Process "https://scloud.work/en/about/" }
47 |
48 | Add-XamlEvent -object $WPFButtonAboutWordpress -event "Add_Click" -scriptBlock { Start-Process "https://www.jannikreinhard.com" }
49 | Add-XamlEvent -object $WPFButtonAboutTwitter -event "Add_Click" -scriptBlock { Start-Process "https://twitter.com/jannik_reinhard" }
50 | Add-XamlEvent -object $WPFButtonAboutLinkedIn -event "Add_Click" -scriptBlock { Start-Process "https://www.linkedin.com/in/jannik-r/" }
51 |
52 | # Device selection view
53 | Add-XamlEvent -object $WPFButtonRefreshDeviceOverview -event "Add_Click" -scriptBlock {
54 | $WPFTextboxSearchBoxDevice.Text = ""
55 | Get-RefresDevices
56 | }
57 |
58 | Add-XamlEvent -object $WPFButtonChangeCustomAttributes -event "Add_Click" -scriptBlock { Show-DeviceAttributes}
59 |
60 | # Signel Device view
61 | Add-XamlEvent -object $WPFButtonNewRow -event "Add_Click" -scriptBlock {
62 | $WPFDataGridSingleDevice.ItemsSource += @('{ "Value":"Add a value", "Name":"New Attribute", "Changed":"(*)", "InitValue":"Add a value", "InitName":"New Attribute", "UpdateAttribute":"False" }' | ConvertFrom-Json)
63 | $WPFDataGridSingleDevice.Items.Refresh()
64 | }
65 |
66 | Add-XamlEvent -object $WPFButtonRemoveRow -event "Add_Click" -scriptBlock {
67 | $WPFDataGridSingleDevice.SelectedItem.Changed = "Delete"
68 | $WPFDataGridSingleDevice.Items.Refresh()
69 | }
70 |
71 | Add-XamlEvent -object $WPFButtonResetRow -event "Add_Click" -scriptBlock {
72 | $WPFDataGridSingleDevice.SelectedItem.Changed = $null
73 | $WPFDataGridSingleDevice.SelectedItem.Name = $WPFDataGridSingleDevice.SelectedItem.InitName
74 | $WPFDataGridSingleDevice.SelectedItem.Value = $WPFDataGridSingleDevice.SelectedItem.InitValue
75 | $WPFDataGridSingleDevice.Items.Refresh()
76 | }
77 |
78 | Add-XamlEvent -object $WPFButtonSave -event "Add_Click" -scriptBlock {
79 | $WPFDataGridSingleDevice.Items | ForEach-Object{
80 | $item = $WPFDataGridAllDevices.SelectedItems[0].Details | Where-Object {-not($_.Changed -eq "Delete" -or $_.Changed -eq '(*)')}
81 | $item | Add-Member -NotePropertyName $_.Name -NotePropertyValue $_.Value -Force
82 |
83 | Set-IDIDevice -IDIDevice $item
84 | $_.Changed = $null
85 | $_.InitValue = $_.Value
86 | $_.InitName = $_.Name
87 | }
88 | $WPFDataGridSingleDevice.Items.Refresh()
89 | #Get-RefresDevices
90 |
91 | Hide-All
92 | $WPFGridDeviceFinder.Visibility = "Visible"
93 | }
94 |
95 | # Multi devices
96 | Add-XamlEvent -object $WPFButtonNewRowMulti -event "Add_Click" -scriptBlock {
97 | $WPFDataGridMultiDevices.ItemsSource += @('{ "Value":"Add a value", "Name":"New Attribute", "Changed":"(*)", "InitValue":"Add a value", "InitName":"New Attribute", "UpdateAttribute":"False" }' | ConvertFrom-Json)
98 | $WPFDataGridMultiDevices.Items.Refresh()
99 | }
100 |
101 | Add-XamlEvent -object $WPFButtonRemoveRowMulti -event "Add_Click" -scriptBlock {
102 | $WPFDataGridMultiDevices.SelectedItem.Changed = "Delete"
103 | $WPFDataGridMultiDevices.SelectedItem.UpdateAttribute = $true
104 | $WPFDataGridMultiDevices.Items.Refresh()
105 | }
106 |
107 | Add-XamlEvent -object $WPFButtonResetRowMulti -event "Add_Click" -scriptBlock {
108 | $WPFDataGridMultiDevices.SelectedItem.Changed = $null
109 | $WPFDataGridMultiDevices.SelectedItem.UpdateAttribute = $false
110 | $WPFDataGridMultiDevices.SelectedItem.Name = $WPFDataGridMultiDevices.SelectedItem.InitName
111 | $WPFDataGridMultiDevices.SelectedItem.Value = $WPFDataGridMultiDevices.SelectedItem.InitValue
112 | $WPFDataGridMultiDevices.Items.Refresh()
113 | }
114 |
115 | Add-XamlEvent -object $WPFButtonSaveMulti -event "Add_Click" -scriptBlock {
116 | # Check if there are changes to apply
117 | if(($WPFDataGridMultiDevices.ItemsSource.UpdateAttribute | Where-Object {$_ -eq $true}).count -lt 1){
118 | Show-MessageBoxInWindow -text "No change to apply. Please check add and mark an attibute to change." -button1text "OK"
119 | }
120 |
121 | $WPFDataGridMultiDevicesSelected.ItemsSource | ForEach-Object{
122 | $device = $_.Details
123 | foreach($item in $_.CustomInventory){
124 | $device | Add-Member -NotePropertyName $item.Name -NotePropertyValue $item.Value -Force
125 | }
126 |
127 | foreach($inventory in $WPFDataGridMultiDevices.ItemsSource){
128 | if($inventory.CurrentItem.Name -eq 'New Attribute' -and $inventory.CurrentItem.Value -eq 'Add a value'){continue}
129 | if($inventory.UpdateAttribute -ne $true){continue}
130 |
131 | # if($inventory.Name -in $device.PSObject.Properties.Name){
132 | if($inventory.Changed -eq "Delete"){
133 | $device = $device | Select-Object -Property * -ExcludeProperty $inventory.Name
134 | }else{
135 | $device | Add-Member -NotePropertyName $inventory.Name -NotePropertyValue $inventory.Value -Force
136 | }
137 | Set-IDIDevice -IDIDevice $device
138 | }
139 | }
140 | $WPFDataGridMultiDevices.ItemsSource = @($WPFDataGridMultiDevices.ItemsSource | Where-Object {($_.Changed -ne "Delete" -and $_.Changed -ne '(*)')})
141 |
142 | $WPFDataGridMultiDevices.ItemsSource | ForEach-Object {
143 | $_.UpdateAttribute = $false
144 | $_.Changed = $null
145 | $_.InitValue = $_.Value
146 | $_.InitName = $_.Name
147 | }
148 |
149 | $WPFDataGridMultiDevices.Items.Refresh()
150 | $WPFDataGridMultiDevices.ItemsSource
151 |
152 | Show-MessageBoxInWindow -text "Changes saved. New fields will show up after a manual refresh." -button1text "OK"
153 | Hide-All
154 | $WPFGridDeviceFinder.Visibility = "Visible"
155 | #Get-RefresDevices
156 | }
157 |
158 |
159 | # Device Actions
160 | Add-XamlEvent -object $WPFButtonDeviceSync -event "Add_Click" -scriptBlock {Invoke-IDIDeviceSync -IDIDevice $WPFDataGridAllDevices.SelectedItems[0].Details}
161 | Add-XamlEvent -object $WPFButtonDeviceRestart -event "Add_Click" -scriptBlock {Invoke-IDIDeviceRestart -IDIDevice $WPFDataGridAllDevices.SelectedItems[0].Details}
162 | Add-XamlEvent -object $WPFButtonBitlockerRotation -event "Add_Click" -scriptBlock {Invoke-IDIDeviceBitLockerRotation -IDIDevice $WPFDataGridAllDevices.SelectedItems[0].Details}
163 | Add-XamlEvent -object $WPFButtonDefenderScan -event "Add_Click" -scriptBlock {Invoke-IDIDeviceDefenderScan -IDIDevice $WPFDataGridAllDevices.SelectedItems[0].Details}
164 | Add-XamlEvent -object $WPFButtonDefenderSignature -event "Add_Click" -scriptBlock {Invoke-IDIDeviceDefenderSignatures -IDIDevice $WPFDataGridAllDevices.SelectedItems[0].Details}
165 | }
166 | function Set-UiAction {
167 | # Search
168 | Add-XamlEvent -object $WPFTextboxSearchBoxDevice -event "Add_TextChanged" -scriptBlock {
169 | Search-DevicesInGrid -searchString $($WPFTextboxSearchBoxDevice.Text)
170 | }
171 |
172 | Add-XamlEvent -object $WPFDataGridSingleDevice -event "Add_CurrentCellChanged" -scriptBlock {
173 | if($this.CurrentItem.Name -eq 'New Attribute' -and $this.CurrentItem.Value -eq 'Add a value'){
174 | $this.CurrentItem.Changed = '(*)'
175 | return
176 | }
177 | if($this.CurrentItem.Name -ne $this.CurrentItem.InitName -or $this.CurrentItem.Value -ne $this.CurrentItem.InitValue){
178 | $this.CurrentItem.Changed = '*'
179 | }
180 | $this.Items.Refresh()
181 | }
182 |
183 | Add-XamlEvent -object $WPFDataGridMultiDevices -event "Add_CurrentCellChanged" -scriptBlock {
184 | if($this.CurrentItem.Name -eq 'New Attribute' -and $this.CurrentItem.Value -eq 'Add a value'){
185 | $this.CurrentItem.Changed = '(*)'
186 | return
187 | }
188 | if($this.CurrentItem.Name -ne $this.CurrentItem.InitName -or $this.CurrentItem.Value -ne $this.CurrentItem.InitValue){
189 | $this.CurrentItem.Changed = '*'
190 | }
191 | try{
192 | $this.Items.Refresh()
193 | }catch{}
194 | }
195 |
196 | # Device
197 | Add-XamlEvent -object $WPFDataGridAllDevices -event "Add_MouseDoubleClick" -scriptBlock {
198 | Show-DeviceAttributes
199 | }
200 | }
201 |
202 | ### Actions
203 | function Search-DevicesInGrid {
204 | param (
205 | [Parameter(Mandatory = $true)] $searchString
206 | )
207 |
208 | if ($searchString.Length -lt 2 -or $searchString -eq '') {
209 | Add-DevicesToGrid -devices $global:allDevicesGrid
210 | return
211 | }
212 |
213 | $allDeviceSearched = @()
214 | $allDeviceSearched = $global:allDevicesGrid | Where-Object `
215 | { ($_.DeviceName -like "*$searchString*") -or `
216 | ($_.DevicePrimaryUser -like "*$searchString*") }
217 | Add-DevicesToGrid -devices $allDeviceSearched
218 | }
219 | function Add-DevicesToGrid {
220 | param (
221 | [Parameter(Mandatory = $true)] $devices
222 | )
223 |
224 | $items = @()
225 | $devices = $devices | Sort-Object -Property DeviceName
226 | $items += $devices | Select-Object -First $([int]$($WPFComboboxDevicesCount.SelectedItem))
227 |
228 | $WPFDataGridAllDevices.ItemsSource = $items
229 | $WPFLabelCountDevices.Content = "$($items.count) Devices"
230 | }
231 |
232 | function Show-MessageBoxInWindow {
233 | param (
234 | [String]$titel = $global:ToolName,
235 | [Parameter(Mandatory = $true)]
236 | [String]$text,
237 | [String]$button1text = "",
238 | [String]$button2text = "",
239 | [String]$messageSeverity = "Information"
240 |
241 | )
242 |
243 | $global:message = [SimpleDialogs.Controls.MessageDialog]::new()
244 | $global:message.MessageSeverity = $messageSeverity
245 | $global:message.Title = $titel
246 | if ($button1text -eq "") { $global:message.ShowFirstButton = $false }else { $global:message.ShowSecondButton = $true }
247 | if ($button2text -eq "") { $message.ShowSecondButton = $false }else { $global:message.ShowSecondButton = $true }
248 | $global:message.FirstButtonContent = $button1text
249 | $global:message.SecondButtonContent = $button2text
250 |
251 | $global:message.TitleForeground = "White"
252 | $global:message.Background = "#FF1B1A19"
253 | $global:message.Message = $text
254 | [SimpleDialogs.DialogManager]::ShowDialogAsync($($global:formMainForm), $global:message)
255 |
256 | $global:message.Add_ButtonClicked({
257 | $buttonArgs = [SimpleDialogs.Controls.DialogButtonClickedEventArgs]$args[1]
258 | $buttonValues = $buttonArgs.Button
259 | If ($buttonValues -eq "FirstButton") {
260 | return $null
261 | }
262 | ElseIf ($buttonValues -eq "SecondButton") {
263 | return $null
264 | }
265 | })
266 | return $null
267 | }
268 |
269 | function Show-DeviceAttributes {
270 | $selectedDeviceItem = $WPFDataGridAllDevices.SelectedItems
271 | if ($selectedDeviceItem.count -lt 1) {
272 | Show-MessageBoxInWindow -text "Select a Item" -button1text "OK"
273 | return
274 | }elseif ($selectedDeviceItem.count -eq 1) {
275 | Show-SingleDevice -selectedItem $selectedDeviceItem[0]
276 | return
277 | }else{
278 | Show-MultiDevices -selectedItems @($selectedDeviceItem)
279 | }
280 |
281 | }
282 | ### Views
283 | function Show-SingleDevice{
284 | param (
285 | [Parameter(Mandatory = $true)] $selectedItem
286 | )
287 | # Change UI
288 | Hide-All
289 | $WPFGridShowDevice.Visibility = "Visible"
290 | # Set lables
291 | $WPFLabelHostname.Content = $selectedItem.Details.deviceName
292 | $WPFLabelDeviceCompliance.Content = $selectedItem.Details.complianceState
293 | $WPFLabelPrimaryUserUpn.Content = $selectedItem.Details.userPrincipalName
294 | $WPFLabelEnrollmentDateTime.Content = $selectedItem.Details.enrolledDateTime
295 | $WPFLabelLastSyncDateTime.Content = $selectedItem.Details.lastSyncDateTime
296 | $WPFLabelOsVersion.Content = "$($selectedItem.Details.osVersion) ($($selectedItem.Details.operatingSystem))"
297 | $WPFLabelCategory.Content = $selectedItem.Details.deviceCategoryDisplayName
298 | $WPFLabelModel.Content = "$($selectedItem.Details.model) ($($selectedItem.Details.manufacturer))"
299 | $WPFLabelSerialNumber.Content = $selectedItem.Details.serialNumber
300 | $WPFLabelLostModeState.Content = $selectedItem.Details.lostModeState
301 |
302 | # Show custom attributes
303 | $WPFDataGridSingleDevice.ItemsSource = @($selectedItem.CustomInventory | Where-Object {$Null -ne $_.Value -and $Null -ne $_.InitValue})
304 | }
305 |
306 | function Show-MultiDevices{
307 | param (
308 | [Parameter(Mandatory = $true)] $selectedItems
309 | )
310 |
311 | Hide-All
312 | $WPFGridShowDevicesMulti.Visibility = "Visible"
313 |
314 | # Show selected Devices
315 | $WPFDataGridMultiDevicesSelected.ItemsSource = $selectedItems
316 |
317 | # Create inventory
318 | $inventory = @()
319 | $selectedItems | ForEach-Object {$inventory = $inventory + $_.CustomInventory}
320 | $inventory = @($inventory | Sort-Object -Property Name,Value -Unique | Where-Object {$null -ne $_.Value -and $null -ne $_.InitValue})
321 |
322 | $WPFDataGridMultiDevices.ItemsSource = $inventory
323 | }
324 |
325 |
326 | ### Init
327 | function New-UiInti {
328 | # Set Lable Text
329 | $global:formMainForm.Title = "$global:ToolName - v$global:Version by $global:Developer"
330 | $WPFToolName.Content = "$global:ToolName"
331 | $WPFCreator.Content = "(c) 2022 by $global:Developer (MIT License)"
332 | $WPFLableHeader.Content = "$global:ToolName"
333 |
334 | # Set images
335 | Set-UiImages
336 |
337 | try {
338 | if($global:AuthMethod -eq 'User'){
339 | $WPFLableUPN.Content = (Invoke-MGGraphRequest -URI 'https://graph.microsoft.com/beta/me?$select=userPrincipalName').userPrincipalName
340 |
341 | }else{
342 | $WPFLableUPN.Content = $global:AuthAppId
343 | }
344 | }
345 | catch {
346 | Write-Error "Fail to load profile info: $_"
347 | return $false
348 | }
349 |
350 | try{
351 | $WPFLableTenant.Content = (Invoke-MGGraphRequest -URI 'https://graph.microsoft.com/beta/organization?$select=displayName').value.displayName
352 | }catch{
353 | Write-Error "Fail to load organization: $_"
354 | }
355 |
356 | # Set buttons
357 | Set-UiActionButton
358 | Set-UiAction
359 | return $true
360 | }
361 |
362 |
363 | function Set-UiImages {
364 | #Load images for UI
365 | $iconHome = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABPElEQVRIiWNgGFHAYerzYoepz4tJ0cNIlKr//xmdpr5o/8/wvxwqMsn+jWRhQwPjP4otCG24yvZGRHA+AwNDFJrU2v/cP2MOJCr+INsCt+4X3L85/61hYGDwwK7i/z6OfxyB2/OEP5FsgfOEl+L/WP5uZWBgMCbgwsvM//947M6Ve0a0BXaTXyiyMP3b+f8/gyo+w5HAfSaGfx57c2RuoUswoQs4TntmzMz47zgJhjMwMDAo/mNgOuYw6YUFXgucJr9wYvjHsI+BgUGcBMNhQJiR6d8ep8nPPbFa4DD1WfR/xn/bGRgY+MgwHAa4/zP+3+Q45WkSigUOk5/lM/5nWMTAwMBGgeEwwMLAwDjHYcrTcgYGLJHsOOXZf0pM358jhWImRiRTG9DcAhZiFaJ7ndigHPpBNGrBKKAcAAB1CWAtKzJosQAAAABJRU5ErkJggg=="
366 | $iconSearch = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAgVBMVEUAAAAknvMnmfUomfUomfUnmfUpmvUjl/MomPYomfUomfYnmPYpmvYomfUpmvUol/Muov8omvcnmfUomfUmmfIomfUpmPQnmfQomPUomfUnmfMomfUnnesrlf8omfUkku0nm/MomfUomPUnmfUomvQpmfUomfUnmvYomfUomfX///+Vwm5wAAAAKXRSTlMAFYLO9M+DFlLx8lRR/bBACz+v/hTwd3WBskHQDQzzDkKAs+95fc2IfxLEHQcAAAABYktHRCpTvtSeAAAAB3RJTUUH5gYUDicDFaCpggAAAJ5JREFUKM+tkNkOgjAQRVs2obILtaDsgt7//0GJpHFq9Enm6eTc5GZmGPtnuGU7jusdPr0f4DXiaPowQpykaZbjZCRFCbnRGYK2KVQac3gksFFrzHAhgcBVY4PWCLp3IIyqXmMClwQDRo0xLHr2hNtGEgGnh8zA2DdNXSHyjSbM0/aSMjQ9GFeL0y6qIP6+evnl33t59vjh15UGtsc8AfKLD4mzmPrPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIyLTA2LTIwVDE0OjM5OjAzKzAwOjAwYoXoEAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMi0wNi0yMFQxNDozOTowMyswMDowMBPYUKwAAAAASUVORK5CYII="
367 | $iconRefresh = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACVUlEQVRIid2VQWsTQRTHf283qUkTtXhrUSlSSNLc2i8gqBVPBg89etBKlIpQaAW9NLfWVi8epFv04EkICI1Xix9A7K2lKUUQlfaitEqS1TTZ52G3sk3SJS160P9peDPz/82+nXkP/nVJ0GQ6rx21bTsjOBnUGAA9CSjIZ5QlhYW4RAtLWdnx70ta5VwxG8sFApJzlSuIzgJngs+o66oysXYzVvDMJ4FcMRuT1oCcGqlue1rRCS+yrCJPTeqLoUj8A0DtR6lX1byg6AiQBhRlGuEnkAPYF5CyKjOeeVWEsdWNzjly4rQ8fF7NxJY9KuhDIOyfagnw0vISqKrhXFq7cfRNcHq8ffPl5yhXWwGM3UA6rx1ezhFhrG1zqzzZaO7Xb0Bt287g/tDl1a5Oq21zL+dtLC69SFplTViVO21taFOGbzwAENL64l8CSA8A9fjHPwkINQZqkS+BrztIfY+/HgsdiXwDvhezseOw5wt0AyC0Ezl1WEA43HHaHcnmbswHMN4BOGIOHRaghlwEUHjbBFCh4E7qCHk1D+yeVxPkmmvGqyZAqCu6IPAeSCe37FsH9U9s27eBftD1uEQLTYCVYaki4hU4fdQ/VzrXrnlqvnReVGcBVZFxf/luujEJq/JA0LtAFZXx4onoE4al3sr4bE5Dmz32qGceBpkuZjvv+de0LNeJbnvKgwCsCPIMp/7acNxy7RilXhVzCNHrblpQFZlZ24jeb6y8+975lFXOKMwCffutcbW34TQq8FENWhouqX1ZIIMw6LVMBD6BLDlKoVXL/L/0C7qI6rgG7zcJAAAAAElFTkSuQmCC"
368 | $iconPaging = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB3UlEQVRIidWVMWtTYRSGn/MlanJvM7fYChXE3jgouLk5OTTaQcHVQUxT/AN1sVpE9BfopX9BESXVwdWtOLjYNIsZQm0Hl9qbREzOcWiE9N5EmljBvtPh/V7OA985Hx8cdUncyIdRwSAEJofsVVehWC3673pNF08ZPB+hOcCUGGHcTACAqRGaAyBw6iCAQ9U/Bxx9JdY0CCP7m4aVeX9fz/96yAq8wZhrkzrT/uFlBE7GQ+kRm9dVuF4t+msx/+thAGqptl2q3B3bCp41p3F6D5gFxoFtYBV1TyoL2RoMP+SO4i5X57MfgnD3CrgXYLlkTL6L6Y310tj7BOAgmllpnha1T2A5jLI6ljMZ73Or1TjnjCWgAOxIyp0faQaiugjkMMqVkn+t52gNuBqEURkooLqYAPzpiizN5MZtf5O9O8fgYb+cqiw7ZwUzZoda007kfeuW4wAnPG+9X06OR7/9iaEAx7zWRLfcBvjZbMwEYfQgAVDvbLfc6geoDwKo6oVu+RrA4CWwtC9kJij3AUR4mwCoUBwEEeMWQKptj4AWMN17ng+ji/mVxitgDtixjnuaGHL3T038TL3qpKUEZOK+wUf2VmQXsZuVhWxtpHcwSEEYbRqs4tzjjTvZLwC/AJiinuCuqDEqAAAAAElFTkSuQmCC"
369 | $iconCount = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAAN0lEQVRIiWNgGAUDDRjhrIor/xkYGBgYOnQwxcgBUHOYyDZgxIDROBgFlIPRVDTwYDQORgHlAADHmRgNDUab0wAAAABJRU5ErkJggg=="
370 | $iconAbout = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABZklEQVRIidWVPU7DQBCFn4OSAhEkqCkRByA0cAMoiKMoSsEZOAcEcQQOkdQcgZ86/KTB9EiRaCDio/CEWM6uvQlVnrSytfP2Pc/s7lhadURFQaAmqSWpKWlf0o6F3iU9SupL6kdR9LWwM9AGRpTjFWgtIrwGXAcI53EFVEIMXOJvllHdRgwMHbxeSFlc4tsO7pbF8oh94jXcNW9b/ARIbBzbXMfBfyE9HHMGXQcZoG7xJJuVzW161nSmutlNaXoqNwbQ7IhmseFZ86eVNWh4yHlMJJ3be9fDOZibAcaedPM4M/4h8OnhjJc1uDHuLvBRwHMaPAUY7Bn3toQ3nOpm9+A+oP4jex6V8O5cBoMAgwmApPUS3rwWUCW9JP/FCNdFM5NW0coMz4cf4LQwN9KuuKzBRUnpJKAC9Ioy8Xz5JSHtOmMUE7Ynz4CvzZT+MquSYqW9paFZP0okPSg9LYMoir6Dv3zl8AsKfI8ggolmqwAAAABJRU5ErkJggg=="
371 | $iconChange = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCklEQVRIibWVPW8TQRCGn/EloQhKJEcUiRMaQKKmASHycYmbFFBEgpIuorCdmgbFLqHD5xRxQ0EXBEKAREGIDyjgHyCRVFg+CrAlPkIcJXdDYyNb9tnekzLVrnbmfXZmd3bBwBadStbEHyBmIq4i6ycCiCoOIBHE94EyygdV3XLXEtu94gcuUYuNAhcRViUmb2zHc+celi9EBuxkEllRzTXnwwex036glwTuAd8R5i3L+rSU92a7xfctUdOapSqlp/7HLOerY3U5fISwAtRU/MtuambPKIOwTABer038mq9O3kR5BsRRq9gzg4UN75oEerWUSTwYFAyQ3KyN+0f1XeAMBMlSevpt1wwk4DYi922ncsMEsH0n/hOk0JC81brWDhCuAGhMv5oAAPwgeNkYzoUCFM4CjPwd2jUGjI40D3c6FABYAPtxK0p/NO24F6ACMFw/PGeqeurgqNFs0lbedoDyHkCwrpsCVLVRGv0YClDVrcYgs5yvjpkASpnEC0Xvovq4p6PteK5d8NR2vKfZrHachemf0CHgB/4qUENYeTfx7Ulyszbeuq4i6yaQrm/RUt6bDWI8B+LADxBH0FeBDH0RPf4NIKq5nUyiLyj0sVvYKJ9HraKAHRo8ACT0vrupmT03PbUIQRIoCnwG/vTbcccmTAPsgqcAKpJ1U5O5fv6ROnZQ8Uhmek3/AehKxwor05QOAAAAAElFTkSuQmCC"
372 | #$iconShow = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABV0lEQVRIie2UPU/CUBSG35dCWDq4InTwLzgbUjYTNj9WN1kUE3+Jg9aIcXKG0XRRgcHR32CiWBMmVqPc42CutDeVloIxMT5b33N7Pu89wD8JMPxRO32pQ0kLQDmjvwGFjdtmyddCLmJWcjaHcwCoCKUVFnLmgTmca5xpARbOjwfIx4nd/WXG6UnUTgIxtT/aIo0uWbcsrgVhexy/W4GZWZbhTw0wK0Ju50TVUwcwZ5BEb6/UBtBOHeA7XC9Yo8IOiCom62UAoA+Ry26zfKfPRrdpQsbu0cMSC8ULAJvTEiDQVm+vu73DlVHqW+R6Q5uF4k3EueCK1rgiSjlC+BMZWywUr11vaJsVPMHYqF9v4DjogNgI20Qpp3dQGQBA1Xt2LOGjkVcnUgGFDXz2cmGkvteuN7Qp730Aq1oTwsdYNZRFWsJzAOuhX+6FeXemh5NlyJnW8izX9AN/r4CCsuo/AAAAAABJRU5ErkJggg=="
373 |
374 | # About
375 | $iconLinkedIn = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABJ0lEQVRIie2Sr07DUBSHv9N1YmuxGCAMFG5BIJBYeAhCSOpm2wUMju0FyOawPMEegQSFJYHQsTkUCd34k/UgCKQ0KdDugoHP3T/n9+Wee+CfL5DkYrEVbQl0EeaKhCkMUbx+0+m97VkpXado+Gs58yJ0k3tW+kLR8AQLmYIUV4juCBxNY8sUiGon9N3j63G1ATwXFdhZB2pZu0ut0Y1KtA5SNi5AdUWFk9Sg5SazRSrsh4EjYeC8G+xJPBsGjqiySRxvMK5WEF0Fucgt+Ix+0+k9Pbpn4YE8hL57LqhvVLB8GK2VK6O7Wvt+G6A0iU+NCrREXcBGqQNc7s3cGhUQf//niwly8OOCD0+ttSM1EZoc7d9tkcLQQOYgU4DiTSkZIOpNUf8XeQFjeFM4aqoyewAAAABJRU5ErkJggg=="
376 | $iconTwitter = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACA0lEQVRIie2SP2gTcRTHP+9y1+Qutf4dxCEJHXTUooOtWLqJILqIgrMgAXFqbMUlXUzqqCCosyIu4uBSFBHxz6A4ZQnFFrVasQ62uYsxXp5L06bJ5Uy66NDv+H3vfb7v9/jBhv61pNuBXVl1LNsbFzgDJICPCnd8y8ltXsR3Y95RhH2zY/GJloBk3r1mOU5m+oJU2sF7bO8JcDCg/B7YDiyYyMj0mPMJwFgpqwpC+nfZu79n8tumoADL9sbbwAH6gajC06rqibq5GiCiAovA8QrOu8RV71AzYfksYYqBRHdsdW41zKwqOeleFzjfYL0CvR2pmc8r0eicWfWWgEhIgM5edCKIaN0wG6u9ZeeSa7t7QQ4vW4Mgg77hY1a9vywPIKVGODSeCHBjXqYGE8DLDmgBqn1pdtYEqCH9BvIYGFpfAG9DA3o9Oy3wYp1wFB6FBhSyUpopO8MKZ4HXXfLnq+X4g2bTbDZStncPOADs7IYuIlc+Z6XlJxjNRtX300ARsDumK1MzKftGYHC7mWTOHRBDToNmghZpUKEn4o8UR/sWgoprTrT/plrz35f6TMvcbfh6TNFzoXBlyqByqji67Ue7lpUXpPKlI4jkgIGQbev6IKqXZ37G75KVWlhjy4mSeW9IqJ0UQ4ZVSQBbFL6KMicGb1B96JTjzwpZ+dXBIhv6D/QH8mKgEDaLDDsAAAAASUVORK5CYII="
377 | $iconWordpress = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAD8ElEQVRIid2VX4iUVRjGf+/5Znfn+2azxT/4J9dZKQpUSDKKErUugoLIyXDcQiMCk4T+XLRuRhcDEbVtGHQRtOJVRbUG6yaBN1uURX/MyEgrMXe2VSMoKXd3vtnd+c7Txcza6mxSl3Uun/Oe53nf5z3nPfBfX3apzeUFNZbScU7mc+CuAy2uHTsF/iuT2xeVw31HCzbxrwWyXaUNhrqBH83oRcnBkrvsFEA4OdJqLljjIW/GUjnrGOqI+v6ZQEEuG8bPG369KdiapCq/usS2gt0CtAENiCHBQCrwPYlSc1CyG3N9xVK4k4L56XSuLvMq+Y0Y62TJ3S5x34I9DqwEWoAMxjIzHkm8O2LyeVVsHfI3tUXxcxfzXSBQtcWvD5w2gnujRnypPjlJw5OT0R+VhiQnaUNb11huRouWF9Q4FpaOmdyDiSUNzuweZOdA90ocx9nXJs3CSGr4JiCbCqPwxKM2Xk1w5FZwPXNbomWHt9nkBRWU0nEOOBmV05/91Nk8UNyR2Y40D5hvxgqkuNiZ2VbckdkuNAeYD1hSHlt3PlufGnTY0Nnf4/V1Fsl8zozeUljqvOplNQGYYxBoAhaYeGzxLoW16O+BEECyFygoVWXzD2HqBXJ1AsAqlByUaJ8sx3dWeZK908yclarEdwF47E0gqe1cm02XblteUDNwhyrJR4LrZxBwCxsZP4NZq0n3Awx2zvoB+OZ8iDQOMNyZOYMYOI8Hlo7T5ZVAW1MwfhpYNFMFUywp4ParXzw3F0DQWythhDg6MGWTodemTjR4DsmUq+e6QMD/PEHTIsyGgMbJJNUOECTB3ppF+2kqLwgm4ocBSkGmTzCKOHZiR3haaCNQLJNeDJypFzD3JQrWmvR+NXNtATj5VPo4cARpLy7ZZFbFf+mwMbA+zPYv6Y5vBpYYNmBya2UcqhMwT7+HPFIP4IEblnaduwbAzPYQRwfA2oGVV3aPrABw5l/3xn4nnwe8J9mDs7wT/XUCUTncVx1cQQvQU0WDLQCDbeErFo5kqY4LEh9sruKZgXmXh1+AbZTZLjwLkW+d3RLWCxwt2IScdaBkdwLPgH0g2ExBjrwlImj/q1+6bwr/7ezoauCIl14y53oMe2LqFV/UZBjqiPow1xegtxK02eC9pVF5ddVDmyZAazYcrb5gF5StogccvA30DnZm3p3OWXdNi6VwJ+Y+dfChx78TldKfZ58dW4gwIAZKwHdOdgWAfBIpZZ+Y2cfFOHr6Yr6/nZRtXWM5QbfDhjD1evmDzXHzMMBI8+gSV3FrcJZHvhVZR/HJTP9MPJf8Mle9qoba4MrJWCVVv0yDYZkddqJ/dkvYP93z/9/6E+aHsqs7a3d1AAAAAElFTkSuQmCC"
378 | $iconBlog = "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"
379 |
380 | # Device
381 | $iconDeviceSync = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCklEQVRIibWVPW8TQRCGn/EloQhKJEcUiRMaQKKmASHycYmbFFBEgpIuorCdmgbFLqHD5xRxQ0EXBEKAREGIDyjgHyCRVFg+CrAlPkIcJXdDYyNb9tnekzLVrnbmfXZmd3bBwBadStbEHyBmIq4i6ycCiCoOIBHE94EyygdV3XLXEtu94gcuUYuNAhcRViUmb2zHc+celi9EBuxkEllRzTXnwwex036glwTuAd8R5i3L+rSU92a7xfctUdOapSqlp/7HLOerY3U5fISwAtRU/MtuambPKIOwTABer038mq9O3kR5BsRRq9gzg4UN75oEerWUSTwYFAyQ3KyN+0f1XeAMBMlSevpt1wwk4DYi922ncsMEsH0n/hOk0JC81brWDhCuAGhMv5oAAPwgeNkYzoUCFM4CjPwd2jUGjI40D3c6FABYAPtxK0p/NO24F6ACMFw/PGeqeurgqNFs0lbedoDyHkCwrpsCVLVRGv0YClDVrcYgs5yvjpkASpnEC0Xvovq4p6PteK5d8NR2vKfZrHachemf0CHgB/4qUENYeTfx7Ulyszbeuq4i6yaQrm/RUt6bDWI8B+LADxBH0FeBDH0RPf4NIKq5nUyiLyj0sVvYKJ9HraKAHRo8ACT0vrupmT03PbUIQRIoCnwG/vTbcccmTAPsgqcAKpJ1U5O5fv6ROnZQ8Uhmek3/AehKxwor05QOAAAAAElFTkSuQmCC"
382 | $iconDeviceRestart = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACi0lEQVRIidWUT0jTYRjHP89vmwllEFNcm0WQEGi3dQ5nYdBFd1DoUlBBiBsEqVGXfp7KpEtOQ8h7KZjr4CHR6TWyk+siQX90Wzi9mAjT/Z4ObjK3uaY3v6eH9/0+38/78v6B4y7JFs2DK6aKPCtmigTcez5fKPYXWEb5ishkdXJ9ctxsTB0EMLLFbNBjimpfGYs6CVxCuAX6Pll95ltzKOb/L6BciGPLOAVyWZWHKIvARYUJ31C83zTVyPfb8wcs5ITkD+boU49rE4gC0fYxDa2txjpV5RWqvfM1CYDHuf59WU2DsTYRPgAplHcIt2H/GRSTL7R8DYwpoEIV/1zQPZmd29uSd0QdIjIAIKKPIkH3HRUxSwVnFQnUzYhq926vDHhH1FEAqNpJtILWoyw6a9xvAOa6zvaVefA4a93DQBS0fjcrDyCqfgCFt+Mdks6OzwY9Ze1ivEPSCKOZrLYCAOAFsKlMlxNYTGoY2d4rxQAegLTN9uuogO0K28/crHzArqlSSt6YUjq9aWR7rWKAGEDl5s65owK2HNvnM2W8GOALgBraclQAlt7IVJ+LACQMoHC/fUxth81uH1ObqN4FUJGPBYDq5Pok8B1oXFuNdR4WsPonHgAaRFjasLvCe8vONTWHYn6FCSAF1s1IoG6mnPCm1yvXxZApwI5qWyTo2dtBwY3xDcX7Ue0FUqLa7ax1D+c+vH3BptpxxrtEGAAcqryYC7qf5HoKAKapxnxN4nkGAhBFGFXDmMYyfgBgWBckbbUA94AGQAV5eTXpemqaYuXmHXjnd39WGQCtP8gDIMKSqvZEAp5w0flSzd4RdVTtJFozf4sXqMs0/UZYsJDwht0VXngg26Vyjrf+AYUP8PvzHZOfAAAAAElFTkSuQmCC"
383 | $iconBitlockerRotation = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC7klEQVRIidWVTWxMURTHf+d1pq1ESRiMaQkNG8RCIyQSZhohWBiSComNj0Skrz5SJGz6dqXd6VRIiB1SIcZCfMQMCwkNsVELJSTMDOlUaNNgat6xmI8+7cwLds7qvnP/5/8/97xz7oX/3cRts8nqq0z7poVRDSMsA+qALPABeI5qj2/wy+2r1uLMXwuEupNbUTqBetcMhX7AjDUH7hZ8jV0JK9ZSawEY4wMsS41QJNGBcg2oR3mhyiGQJd5vxuRqu2pqVo2lKnIEeKnKQlXuBLtTbQDB7lSbirSVPUEokugAOQpkEDm8ZsB/1rLELpV90FKP+FIHgHagEngErAKImwGZIJAvyzUgI2psiLX4Y27lKcadSW3C1huAp+ArCBQdTVZfZTpXcxA5HDPHyIMX31YzUtUusB1FgSs6+ceJB7vmfweQrL1cRYpcTis6075pYdB6lBe+mf5zv6Fy5AeBy4AgHJKRKgVaG7sSlrPmZQXyrYjC+avbJOsECWwHLsXNwE6AUFcSYAfQmu8Wq5zAWBcJDQAVKvdK4Pyi+noMqq8QZpcjHZdczkKR5BBQk5nknfJoz4zhgj8YSfYKLC8T/yRuBla6CTh/TMmhcyEHWOH82HB6cMp348dXYChuBqbC74OWBKge+TnHLSM3++YdnZtfpgo+p8BTADV03b8KYOv6/Kq3hIBEART2NvVoxd9yN/VohajuBlCRmxMEfOnPN4A3wOLBgeR+R2wv5Uz1cWE58CllAotE6B/2+KPFtJ34xkhyi8J1IAP2xrhZd/9Psg+eTqwVQ24BHlTD8Zba4gkmXnbdqVOoHgMyonpk+qzAmfGDVyS21MP0VLMInYBXlZMPWgLHnZgJApalxsMZH9vzIgB9CBfUMO5hG+8AMOx5krXXAXuARYAK0rE67T8x/uYt++AEu5JhEekEXVAOA7kHR1WPxs3aaMl9t+CGc+qt+flxs6iGgQZyTyYC7xGe2Uh02OOPPtsno248/7f9Aq1TEoOuYXfCAAAAAElFTkSuQmCC"
384 | $iconDefenderScan = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACEklEQVRIidWVPWhTURTH//+0eQiaJSaQvDpJcelmBMcki459LdRFcC3I6+CQqujwRtsHopgOHRwdpEiTgoIIpp014lIXPwYrj0CfRaxDyddx8CU831dftEvPdN+95/5/59x73j3AcTdGLc4Z24qdSWsQ0UCcB3DGWfoGwTuQtYy9V1szptojA8or1iwEJoCzhwT5mUDlta6uxwIYhiS2MtY9gBVn6gOAxz1JvFIU5Wuvc/AjOFQuF3dztw2Dfff0uNfPJX4g5N1sNvdg7Qp7w8yqVnAeIotb2RYA3AzNwDmWZwDaSHC2cT3/3KtTrloSTBhwMLO5oNYG34nBYM7YVpwzB0Cb7L6PEgozkmZhVZI+gJ1JaxheqKjSG3t5+f5OenSETKa6rWkfgMCMx3OqrYy9uGS2To6KoIjmA4BSCPC92DnRf1oyxFcMh9iFoexgUK5a+wBOhWx4UrTz17wl6NrrvfhfDV1NAe4Mou3q5unWw5i+ADAMxJ26BeBc2A5S9HLV0mMChj+LO4O3ccOLYW8CAKwflbqQGz7Az2RuHeCnI9D/kt3d8wOa8+wQsvif4gKRG+7n+68q+vPkivnP6oKlxsLEhnvOV6ZFW70FcnlUbYJLpe/5O96F0IZTemRpJE1AJqOUSXwUkUpDnwgsksiWWViVZKrbmnbelgKclklgB0SzD9b3x3P15jw7UTrH234D+7qw57KK2MsAAAAASUVORK5CYII="
385 | $iconDefenderSiganture = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABw0lEQVRIid2UsW7TUBSGv3ObDlVeoHYKXVGkblURTEkjMbDDWMKCVDXuhhALYoaJkFCVhYpu9AGQQCUWiKVrpSpjq1YxL5BaamsfBmrJctyb1EzwS3f57/H333uObfjXJVmj3gnug24Cc9dknYjKk2+e8zltmtG6QnCAORXdzJo5AYXgiW5MEjBeIo+A4SSlhQJ6a85HVRYF9v824AzVZyW9qPRariQLwPfcfik0dwTdsgFKtk1FX/he5dW4UxYOmNZoG6DeGfwE7iZ+r+VKox1Uz028A1K1Mawt+urdHDTaQTUNB6h1g2ZsdA+YV5HHhQNSoGEaJKofgLKJZclfc6wzsAZcgo7yQCr82F13DpY3jis2hnUGgm6VwqnWl6ezQ/jT+2yNRlMrhQNsWt44rlzCX1434AiYB1CkeT4TLzXawYPddeeg3hloUqRRLu8wa4zMQFRWgdOUVY2N7tW6QdN2UuBUhdURXl5l7e3glhE+KSyMgSbqR2oefvdmR34duW+R77n98Cy6DbyfAL49HZrFPDhccYO06t1gBdV3QDmzFary3PfcN7bnxwZAbsuubEmhAIB7r3+VL2aiDkD62/j/9Ru9Zqiay1/nrAAAAABJRU5ErkJggg=="
386 |
387 | $iconSave = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABC0lEQVRIie2UPVICQRCFv96f8gwsHkYIvQMp0ZqKiaUYsRqyiWaWngMOgytnYF1sEyiXmbEcWDfCL+s3/fpVz1QN/PMLUi96+fIc9Ano7jVEdTy76N66zoLdUh/3HQ6gIjf9aeEMMDZ4V4B5moir2WTb/z1MslnauaprxgbNUHTUz5eT1gK2Ia0GmEQu0bzbJrS+wfEErEAuqzhOqjhOUB0BpY/R+cg2cj1POw814f4sL0SQyY+WDV4bqIQvdmb87OP1ClhHYn0dQbAK/ywgKsuBJX6Gtuby+jQhctebFkSsXwE+JByoMj4k4A04dfSdIJJVRBkYX7DNol7sXJGoDDchh7JQYdjAf4x8AQtPRQ5j4u9sAAAAAElFTkSuQmCC"
388 | $iconNewRow = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAApklEQVRIie2UwQ3CMAxFnyumqOgu9MYkvcA45QKLcCu7VMoa5pJUxKJAQqGq1HdLZP1vx45h5Q3yeNid3F6UC7DN1OtVaG6H8houishNOX8hDlB5jYGNDQDojqWQQd06DRqBYiR2MpIM6tapz/I3Bjks38BOUcTYe9v7V1M3bwU2s5B5yj9ZfpPn7YElZ0f9vYIeqFL3zRONgagCFRobkCruNVY+5w4EICu+rqoZ4wAAAABJRU5ErkJggg=="
389 | $iconRemoveRow = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABzUlEQVRIidWSwU4TYRSFvzuFhMgLtLUsfAFc4Lo6w0p2rHgBwwI7WmJ0pU0jmhgUEtOxCe9A2GjixtLq0u4krl0UB2J8AJT0Py6aJjJOkba66Fn9/39u7v3PPQcmHfb7xY+OlkA7QGHIPocmW90Pc2+ThHf2OlJzgIJMO2nEVLIQoFnKW7LQj2L9hZtLG+ClPf5LTP6ARIp6ex4Vaf78dwXJFA38yQVSlIrJMNnzuldPfnYvpXGpKxoWjbW5T4O4kVJksFv8nlupVs0FUbwscRfjGoDgI3jbrVL2DYy2om/e9MytatWcH33dFOxhXAdmgVkD33Cv/Vr8eKCCi6QoiOJlwR5wgtk9pEpPgW0YegHMmGxpZJMFZQAz3W/eztU9Z4HnLGiVcq9MegDgTOvjmLwAIFmlWDt+3wizBwCL9c68c1YBMFgYJ6b9QChjcv1Hh/cDcdrnkgoOgcJ5aSrWjq98CLNfBG0D36Qn+2H+82K9M3/anVZzLXvgR/Ez4CXQPqPAZKtA57xvT3lupXfytgFk9jyI4rJzmUbGXCOI4jKwCUhOW3+kZRjciOINg4cplCQetcL807EGAAS1o5vOtG490wW05bTVunP5HcAvysG6Vtp3MgsAAAAASUVORK5CYII="
390 |
391 |
392 |
393 |
394 | # Add image to UI
395 | $WPFImgHome.source = Get-DecodeBase64Image -ImageBase64 $iconHome
396 | $WPFImgSearchBoxDevice.source = Get-DecodeBase64Image -ImageBase64 $iconSearch
397 | $WPFImgRefresh.source = Get-DecodeBase64Image -ImageBase64 $iconRefresh
398 | $WPFImgMaxDevices.source = Get-DecodeBase64Image -ImageBase64 $iconPaging
399 | $WPFImgDeviceCount.source = Get-DecodeBase64Image -ImageBase64 $iconCount
400 | $WPFImgButtonAbout.source = Get-DecodeBase64Image -ImageBase64 $iconAbout
401 | $WPFImgChangeCustomAttribute.source = Get-DecodeBase64Image -ImageBase64 $iconChange
402 | #$WPFImgShowCustomAttribute.source = Get-DecodeBase64Image -ImageBase64 $iconShow
403 |
404 | #About
405 | $WPFImgTwitter.source = Get-DecodeBase64Image -ImageBase64 $iconTwitter
406 | $WPFImgWordpress.source = Get-DecodeBase64Image -ImageBase64 $iconWordpress
407 | $WPFImgLinkedIn.source = Get-DecodeBase64Image -ImageBase64 $iconLinkedIn
408 | $WPFImgBlog.source = Get-DecodeBase64Image -ImageBase64 $iconBlog
409 |
410 | $WPFImgTwitterFs.source = Get-DecodeBase64Image -ImageBase64 $iconTwitter
411 | $WPFImgWordpressFs.source = Get-DecodeBase64Image -ImageBase64 $iconWordpress
412 | $WPFImgLinkedInFs.source = Get-DecodeBase64Image -ImageBase64 $iconLinkedIn
413 |
414 | $WPFImgDeviceSync.source = Get-DecodeBase64Image -ImageBase64 $iconDeviceSync
415 | $WPFImgDeviceRestart.source = Get-DecodeBase64Image -ImageBase64 $iconDeviceRestart
416 | $WPFImgBitlockerRotation.source = Get-DecodeBase64Image -ImageBase64 $iconBitlockerRotation
417 | $WPFImgDefenderScan.source = Get-DecodeBase64Image -ImageBase64 $iconDefenderScan
418 | $WPFImgDefenderSiganture.source = Get-DecodeBase64Image -ImageBase64 $iconDefenderSiganture
419 |
420 | $WPFImgSave.source = Get-DecodeBase64Image -ImageBase64 $iconSave
421 | $WPFImgNewRow.source = Get-DecodeBase64Image -ImageBase64 $iconNewRow
422 | $WPFImgRemoveRow.source = Get-DecodeBase64Image -ImageBase64 $iconRemoveRow
423 | $WPFImgResetRow.source = Get-DecodeBase64Image -ImageBase64 $iconDeviceRestart
424 |
425 | $WPFImgSaveMulti.source = Get-DecodeBase64Image -ImageBase64 $iconSave
426 | $WPFImgNewRowMulti.source = Get-DecodeBase64Image -ImageBase64 $iconNewRow
427 | $WPFImgRemoveRowMulti.source = Get-DecodeBase64Image -ImageBase64 $iconRemoveRow
428 | $WPFImgResetRowMulti.source = Get-DecodeBase64Image -ImageBase64 $iconDeviceRestart
429 |
430 | # Fill combo box
431 | $valueGroupCount = "10", "100", "500", "1000", "5000", "10000", "All"
432 | foreach ($value in $valueGroupCount) { $WPFComboboxDevicesCount.items.Add($value) | Out-Null }
433 | $WPFComboboxDevicesCount.SelectedIndex = 2
434 |
435 | # Reset lables
436 | $WPFLableUPN.Content = ""
437 | $WPFLableTenant.Content = ""
438 | }
--------------------------------------------------------------------------------