From ec720fade08865360d172ca22bd5eafac9f60359 Mon Sep 17 00:00:00 2001 From: Achilleas Mitos Date: Fri, 15 Nov 2024 16:27:59 +0200 Subject: [PATCH] Added Remove-TeamViewerDuplicateDevicesV2 example script The script searches for duplicate devices (where duplicate = have the same alias) and removes the devices with an older last-seen timestamp. Resolves JIRA task TEAM-60324. --- Remove-TeamViewerDuplicateDevicesV2/README.md | 44 +++++ ...ove-TeamViewerDuplicateDevicesV2.Tests.ps1 | 50 ++++++ .../Remove-TeamViewerDuplicateDevicesV2.ps1 | 161 ++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 Remove-TeamViewerDuplicateDevicesV2/README.md create mode 100644 Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.Tests.ps1 create mode 100644 Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.ps1 diff --git a/Remove-TeamViewerDuplicateDevicesV2/README.md b/Remove-TeamViewerDuplicateDevicesV2/README.md new file mode 100644 index 0000000..e81be84 --- /dev/null +++ b/Remove-TeamViewerDuplicateDevicesV2/README.md @@ -0,0 +1,44 @@ +# Remove-TeamViewerDuplicateDevicesV2 + +Removes TeamViewer devices (MDv2) that have a duplicate counterpart in the same company. + +The script fetches a list of TeamViewer devices (MDv2) of the TeamViewer company that corresponds to a given API token. +The list will be searched for devices that have the same name (alias). Duplicate devices will be sorted by their last seen timestamp, +and the older ones will be removed. + +## Prerequisites + +This script requires the `TeamViewerPS` powershell module to be installed in at least Version 2.1.0. + +```powershell +Install-Module TeamViewerPS +``` + +## Examples + +### List removal candidate devices + +```powershell +Remove-TeamViewerDuplicateDevicesV2 -WhatIf +``` + +### Remove old duplicate devices. User needs to confirm + +```powershell +Remove-TeamViewerDuplicateDevicesV2 +``` + +### Remove old duplicate devices without further confirmation + +```powershell +Remove-TeamViewerDuplicateDevicesV2 -Force +``` + +## More help + +To get further help about the script and its parameters, execute the +`Get-Help` PowerShell cmdlet: + +```powershell +Get-Help -Detailed .\Remove-TeamViewerDuplicateDevicesV2.ps1 +``` diff --git a/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.Tests.ps1 b/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.Tests.ps1 new file mode 100644 index 0000000..fe69342 --- /dev/null +++ b/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.Tests.ps1 @@ -0,0 +1,50 @@ +# Copyright (c) 2019-2024 TeamViewer Germany GmbH +# See file LICENSE + +BeforeAll { + $testApiToken = [securestring]@{} + + . "$PSScriptRoot\Remove-TeamViewerDuplicateDevicesV2.ps1" -ApiToken $testApiToken -InformationAction SilentlyContinue + + Mock Get-TeamViewerCompanyManagedDevice { @( + [pscustomobject]@{ TeamViewerId = '123456789'; Name = 'unique device'; LastSeenAt = [datetime]'2024-12-16' }, + [pscustomobject]@{ TeamViewerId = 'older device A'; Name = 'duplicate device A'; LastSeenAt = [datetime]'2024-12-17' }, + [pscustomobject]@{ TeamViewerId = 'online device A'; Name = 'duplicate device A'; IsOnline = $True }, + [pscustomobject]@{ TeamViewerId = 'newer device B'; Name = 'duplicate device B'; LastSeenAt = [datetime]'2024-12-18' }, + [pscustomobject]@{ TeamViewerId = 'newer device A'; Name = 'duplicate device A'; LastSeenAt = [datetime]'2024-12-19' }, + [pscustomobject]@{ TeamViewerId = 'older device B'; Name = 'duplicate device B'; LastSeenAt = [datetime]'2024-12-17' } + ) } + + Mock Remove-TeamViewerManagedDeviceManagement -RemoveParameterValidation 'Device' {} +} + +Describe 'Remove-TeamViewerDuplicateDevicesV2' { + + It 'Should not remove any devices if -WhatIf parameter has been set' { + $result = (Remove-TeamViewerDuplicateDevicesV2 -force:$false -WhatIf) + $result | Should -HaveCount 3 + $result[0].TeamViewerId | Should -Be 'older device A' + $result[0].Status | Should -Be 'Unchanged' + $result[1].TeamViewerId | Should -Be 'newer device A' + $result[1].Status | Should -Be 'Unchanged' + $result[2].TeamViewerId | Should -Be 'older device B' + $result[2].Status | Should -Be 'Unchanged' + + Assert-MockCalled Get-TeamViewerCompanyManagedDevice -Times 1 -Scope It + Assert-MockCalled Remove-TeamViewerManagedDeviceManagement -Times 0 -Scope It + } + + It 'Should remove duplicate devices with an older last-seen timestamp' { + $result = (Remove-TeamViewerDuplicateDevicesV2 -force:$true) + $result | Should -HaveCount 3 + $result[0].TeamViewerId | Should -Be 'older device A' + $result[0].Status | Should -Be 'Removed' + $result[1].TeamViewerId | Should -Be 'newer device A' + $result[1].Status | Should -Be 'Removed' + $result[2].TeamViewerId | Should -Be 'older device B' + $result[2].Status | Should -Be 'Removed' + + Assert-MockCalled Get-TeamViewerCompanyManagedDevice -Times 1 -Scope It + Assert-MockCalled Remove-TeamViewerManagedDeviceManagement -Times 3 -Scope It + } +} diff --git a/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.ps1 b/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.ps1 new file mode 100644 index 0000000..89ca3a7 --- /dev/null +++ b/Remove-TeamViewerDuplicateDevicesV2/Remove-TeamViewerDuplicateDevicesV2.ps1 @@ -0,0 +1,161 @@ +<# + .SYNOPSIS + Removes TeamViewer duplicate devices (MDv2) based on their alias. + + .DESCRIPTION + Removes TeamViewer devices (MDv2) that have a duplicate counterpart in the same company. + The script fetches a list of TeamViewer devices (MDv2) of the TeamViewer company that corresponds to a given API token. + The list will be searched for devices that have the same name (alias). Duplicate devices will be sorted by their last seen timestamp, + and the older ones will be removed. + + .PARAMETER ApiToken + The TeamViewer API token to use. + Must be a user access token. + The token requires the following access permissions: company admin + + .PARAMETER Force + If set, the script will NOT ask the user for confirmation of the removal. + The default value is `false`, causing the script to ask the user one more time before starting to remove devices. + + .EXAMPLE + Remove-TeamViewerDuplicateDevicesV2' + + .EXAMPLE + Remove-TeamViewerDuplicateDevicesV2 -WhatIf + + .EXAMPLE + Remove-TeamViewerDuplicateDevicesV2 -Force + + .NOTES + This script requires the TeamViewerPS module to be installed. + This can be done using the following command: + + ``` + Install-Module TeamViewerPS + ``` + + Copyright (c) 2019-2024 TeamViewer Germany GmbH + See file LICENSE + Version 2.1 +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory = $true)] + [securestring] $ApiToken, + + [switch] $Force = $false +) + +if (-Not $MyInvocation.BoundParameters.ContainsKey('ErrorAction')) { + $script:ErrorActionPreference = 'Stop' +} +if (-Not $MyInvocation.BoundParameters.ContainsKey('InformationAction')) { + $script:InformationPreference = 'Continue' +} + +function Install-TeamViewerModule { + $module = Get-Module TeamViewerPS + + if (!$module) { + Install-Module TeamViewerPS + } + elseif ($module.Version -lt '2.1.0') { + Update-Module TeamViewerPS + } +} + +function Remove-TeamViewerDuplicateDevicesV2 { + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] + param([bool]$force, [bool]$is_verbose) + + $devices = @(Get-TeamViewerCompanyManagedDevice -ApiToken $ApiToken) + + $name_to_device_map = @{} + ForEach ($device in $devices) { + if ($null -eq $name_to_device_map[$device.Name]) { + $name_to_device_map[$device.Name] = New-Object System.Collections.Generic.List[System.Object] + } + $name_to_device_map[$device.Name].Add($device) + } + + $name_to_device_map_sorted = @{} + $name_to_device_map.GetEnumerator() | ForEach-Object { + # Sort duplicate devices by LastSeenAt. + # Older devices should go first. + $offline_duplicate_devices = @(($_.Value | Where-Object { !$_.IsOnline -and $_.LastSeenAt }) | Sort-Object { $_.LastSeenAt }) + + if ($offline_duplicate_devices.Count -gt 0) { + if ($offline_duplicate_devices.Count -lt $_.Value.Count) { + # There were some online duplicate devices --> remove all of the offline + $name_to_device_map_sorted.Add($_.Key, $offline_duplicate_devices) + } + else { + # No online duplicate devices --> the last one is the "good" device --> skip it + $devices_to_remove = $offline_duplicate_devices | Select-Object -SkipLast 1 + if ($null -ne $devices_to_remove) { + $name_to_device_map_sorted.Add($_.Key, $devices_to_remove) + } + } + } + } + + if ($is_verbose) { + Write-Information 'All company devices:' + Write-Information ($devices | Format-List | Out-String) + } + + if (!$name_to_device_map_sorted.Count) { + Write-Information 'No duplicate devices found. Exiting...' + + exit + } + + Write-Information 'Found the following devices that have a duplicate alias to other devices in your company, and have been offline for longer:' + Write-Information ($name_to_device_map_sorted | Format-List | Out-String) + + if ($name_to_device_map_sorted.Count -gt 0 -And + !$WhatIfPreference -And + !$force -And + !$PSCmdlet.ShouldContinue('Do you really want to remove those devices?', 'Remove managed devices')) { + Write-Information 'Aborting...' + + exit + } + + $name_to_device_map_sorted.GetEnumerator() | ForEach-Object { + $duplicate_devices_to_be_deleted = $_.Value + + ForEach ($device_to_be_deleted in $duplicate_devices_to_be_deleted) { + $status = 'Unchanged' + + if ($force -Or $PSCmdlet.ShouldProcess($device_to_be_deleted.TeamViewerId, 'Remove device')) { + try { + Remove-TeamViewerManagedDeviceManagement -ApiToken $ApiToken -Device $device_to_be_deleted + + $status = 'Removed' + } + catch { + Write-Warning "Failed to remove device '$($device_to_be_deleted.Name)' with TeamViewerID: '$($device_to_be_deleted.TeamViewerId)'" + + $status = 'Failed' + } + } + Write-Output ([pscustomobject]@{ + Name = $device_to_be_deleted.Name + ManagementId = $device_to_be_deleted.Id + LastSeen = $device_to_be_deleted.LastSeenAt + TeamViewerID = $device_to_be_deleted.TeamViewerId + Status = $status + }) + } + } +} + +if ($MyInvocation.InvocationName -ne '.') { + Install-TeamViewerModule + + $is_verbose = $PSBoundParameters.ContainsKey('Verbose') + + Remove-TeamViewerDuplicateDevicesV2 -force $Force -is_verbose $is_verbose +}