├── Python ├── ppdm_upgrade │ └── README.md ├── ppdm_deploy │ ├── README.md │ ├── config-minimal.json │ └── config.json ├── certmgmt.py ├── secure_login_helper.py ├── assetmgmt.py ├── removeassetsrc.py ├── credsmgmt.py ├── updateprotectionpolicyschedule.py ├── restorevmorig.py ├── policy2dd.py ├── ppdm_exclude_pvc.py └── filelevelrestore.py ├── PowerShell7 ├── Example-19.ps1 ├── Example-11.ps1 ├── Example-04.ps1 ├── Example-18.ps1 ├── Example-03.ps1 ├── Example-17.ps1 ├── Example-21.ps1 ├── Example-25.ps1 ├── Example-15.ps1 ├── Example-14.ps1 ├── Example-12.ps1 ├── Example-23.ps1 ├── Example-05.ps1 ├── Example-01.ps1 ├── Example-06.ps1 ├── Example-07.ps1 ├── Example-02.ps1 ├── Example-08.ps1 ├── Example-09.ps1 ├── Example-10.ps1 ├── Example-22.ps1 ├── Example-27.ps1 ├── Example-24.ps1 ├── Example-13.ps1 ├── Example-20.ps1 ├── Example-26.ps1 ├── README.md ├── Example-16.ps1 └── Standalone │ ├── triageFailedActivity.ps1 │ ├── getLogs.ps1 │ └── getNasFileReport.ps1 ├── README.md └── LICENSE /Python/ppdm_upgrade/README.md: -------------------------------------------------------------------------------- 1 | # PowerProtect Data Manager Automated Lifecycle Management Solution 2 | More info can be found [HERE](https://infohub.delltechnologies.com/p/powerprotect-data-manager-automation-lifecycle-management-2/) 3 | -------------------------------------------------------------------------------- /Python/ppdm_deploy/README.md: -------------------------------------------------------------------------------- 1 | # PowerProtect Data Manager Automated Deployment Solution 2 | More info can be found [HERE](https://infohub.delltechnologies.com/p/powerprotect-data-manager-deployment-automation-deploy-powerprotect-data-manager-in-minutes/) 3 | -------------------------------------------------------------------------------- /PowerShell7/Example-19.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | $Path = @( 16 | "host=192.168.1.11", 17 | "port=636" 18 | ) 19 | # GET A CERTIFICATE 20 | $Certificate = get-dmcertificates -Path $Path 21 | 22 | $Certificate | format-list 23 | 24 | 25 | # DISCONNECT FROM THE REST API 26 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-11.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | # GET A PROTECTION POLICY 16 | $Filters = @( 17 | "name eq `"Policy-VM01`"" 18 | ) 19 | $Policy = get-dmprotectionpolicies -Filters $Filters -PageSize $PageSize 20 | 21 | # START THE POLICY BASED BACKUP 22 | $Backup = new-dmbackup -Policy $Policy 23 | 24 | # DISCONNECT FROM THE REST API 25 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-04.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | 13 | # CONNECT THE THE REST API 14 | connect-dmapi -Server $Server 15 | 16 | # GET THE vCENTER 17 | $Filters = @( 18 | "viewType eq `"HOST`"" 19 | ) 20 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 21 | where-object {$_.name -eq "$($VMware)"} 22 | 23 | $vCenter | format-list 24 | 25 | # DISCONNECT FROM THE REST API 26 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-18.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | $Filters = @( 16 | "hostname eq `"win-iis-01.vcorp.local`"" 17 | ) 18 | # GET AGENT REGISTRATION STATUS BASED ON A FILTER 19 | $Agent = get-dmagentregistration -Filters $Filters -PageSize $PageSize 20 | # $Agent = get-dmagentregistration -PageSize $PageSize 21 | $Agent | format-list 22 | 23 | # DISCONNECT FROM THE REST API 24 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-03.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | # GET ALERTS BASED ON FILTERS 16 | $Filters = @( 17 | "acknowledgement.acknowledgeState eq `"UNACKNOWLEDGED`"" 18 | ) 19 | $Alerts = get-dmalerts -Filters $Filters -PageSize $PageSize 20 | 21 | # GET ALL ALERTS 22 | # $Alerts = get-dmalerts -PageSize $PageSize 23 | 24 | $Alerts | format-list 25 | 26 | # DISCONNECT FROM THE REST API 27 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-17.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | # GET A PROTECTION POLICY 16 | $Filters = @( 17 | "name eq `"Policy-VM01`"" 18 | ) 19 | # $Policy = get-dmprotectionpolicies -Filters $Filters -PageSize $PageSize 20 | 21 | # GET ALL PROTECTION POLICIES 22 | $Policy = get-dmprotectionpolicies -PageSize $PageSize 23 | 24 | $Policy | format-list 25 | 26 | # DISCONNECT FROM THE REST API 27 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-21.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 1 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | # GET CREDENTIALS BASED ON FILTER 16 | $Filters = @( 17 | "name eq `"SYSADMIN`"" 18 | ) 19 | $Credentials = get-dmcredentials -Filters $Filters -PageSize $PageSize 20 | 21 | # GET ALL CREDENTIALS 22 | # $Credentials = get-dmcredentials -PageSize $PageSize 23 | 24 | 25 | $Credentials | format-list 26 | 27 | # DISCONNECT FROM THE REST API 28 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-25.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | # GET AUDIT LOGS BASED ON A FILTER 16 | $Filters = @( 17 | "auditType eq `"SECURITY`"", 18 | "and changedObject.resourceType eq `"/login`"" 19 | ) 20 | $Audit = get-dmauditlogs -Filters $Filters -PageSize $PageSize 21 | 22 | # GET ALL AUDIT LOGS 23 | # $Audit = get-dmauditlogs -PageSize $PageSize 24 | 25 | $Audit | format-list 26 | 27 | # DISCONNECT FROM THE REST API 28 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-15.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | Example-01.ps1 6 | #> 7 | Import-Module .\dell.ppdm.psm1 -Force 8 | 9 | # VARS 10 | $Server = "ppdm-01.vcorp.local" 11 | $PageSize = 100 12 | 13 | # CONNECT THE THE REST API 14 | connect-dmapi -Server $Server 15 | 16 | # GET STORAGE SYSTEMS BASED ON FILTERS 17 | $Filters = @( 18 | "name eq `"ddve-01.vcorp.local`"" 19 | ) 20 | 21 | $Storage = get-dmstoragesystems -Filters $Filters -PageSize $PageSize 22 | 23 | # GET ALL STORAGE SYSTEMS 24 | # $Storage = get-dmstoragesystems -PageSize $PageSize 25 | 26 | $Storage | format-list 27 | 28 | # DISCONNECT FROM THE REST API 29 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-14.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | $Filters = @( 16 | "name eq `"data_warehouse_s01`"", 17 | "and copyType eq `"FULL`"" 18 | ) 19 | # GET ASSETS BASED ON FILTERS 20 | $Asset = get-dmassets -Filters $Filters -PageSize $PageSize 21 | 22 | # GET THE LATEST COPY 23 | $Filters = @( 24 | "assetId in (`"$($Asset.id)`")" 25 | ) 26 | $Copy = get-dmlatestcopies -Filters $Filters -PageSize 100 27 | 28 | $Copy | format-list 29 | 30 | # DISCONNECT FROM THE REST API 31 | disconnect-dmapi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # powerprotect-automation 2 | # Dell PowerProtect - Common Flows 3 | This repository features automation solutions for Dell PowerProtect 4 | ## Authors 5 | - **Idan Kentor** - [Dell Technologies](https://www.dell.com) 6 | - **Cliff Rodriguez** - [Dell Technologies](https://www.dell.com) 7 | ## Supported Platforms 8 | - All supported versions of PowerProtect Data Manager 9 | ## Prerequisites 10 | - Python 3.x 11 | - PowerShell 7.x 12 | ## Documentation 13 | - See the help page of each script (i.e assetmgmt.py -h) 14 | - PowerProtect RESTAPI Documentation - [Dell Technologies API Explorer](https://developer.dell.com) 15 | - PowerProtect Documentation - [Dell Online Support](https://www.dell.com/support/kbdoc/en-us/000196987/dell-powerprotect-data-manager-info-hub-product-documents-and-information?lang=en) 16 | 17 | 18 | -------------------------------------------------------------------------------- /PowerShell7/Example-12.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | $Filters = @( 16 | "name eq `"win-iis-01`"" 17 | ) 18 | # GET ASSETS BASED ON FILTERS 19 | $Client = get-dmassets -Filters $Filters -PageSize $PageSize 20 | 21 | # GET A PROTECTION POLICY 22 | $Filters = @( 23 | "name eq `"Policy-VM01`"" 24 | ) 25 | $Policy = get-dmprotectionpolicies -Filters $Filters -PageSize $PageSize 26 | 27 | # START THE CLIENT BASED BACKUP 28 | $Backup = new-dmbackup -AssetIds $Client.id -Policy $Policy 29 | 30 | # DISCONNECT FROM THE REST API 31 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-23.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | #> 5 | 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | $Server = "ppdm-01.vcorp.local" 8 | 9 | # CONNECT THE THE REST API 10 | connect-dmapi -Server $Server 11 | 12 | # PROMPT THE USER FOR THE DESIERED PASSWORD 13 | $Credential = Get-Credential ` 14 | -Title "Create Linux OS Credentials in PowerProtect Data Manager" ` 15 | -Message "Please enter the password for root" ` 16 | -UserName "root" 17 | 18 | $Body = [ordered]@{ 19 | type="OS" 20 | username="$($Credential.username)" 21 | password="$(ConvertFrom-SecureString -SecureString $Credential.password -AsPlainText)" 22 | name="Linux" 23 | } 24 | 25 | # CREATE THE NEW CREDENTIALS 26 | $Credentials = new-dmcredential -Body $Body 27 | 28 | $Credentials | format-list 29 | 30 | # DISCONNECT FROM THE REST API 31 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-05.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | 14 | # CONNECT THE THE REST API 15 | connect-dmapi -Server $Server 16 | 17 | # GET THE vCenter 18 | $Filters = @( 19 | "viewType eq `"HOST`"" 20 | ) 21 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 22 | where-object {$_.name -eq "$($VMware)"} 23 | 24 | # GET THE Datacenter 25 | $Filters = @( 26 | "viewType eq `"HOST`"", 27 | "and parentId eq `"$($vCenter.id)`"" 28 | ) 29 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 30 | where-object {$_.name -eq "$($DC)"} 31 | 32 | $Datacenter | format-list 33 | 34 | # DISCONNECT FROM THE REST API 35 | disconnect-dmapi -------------------------------------------------------------------------------- /Python/ppdm_deploy/config-minimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "PPDM Config", 3 | "ppdmOVALocation": "C:\\Downloads\\dellemc-ppdm-sw-19.14.0-20.ova", 4 | "ovfToolLocation": "ovftool", 5 | "ppdmVmName": "PPDM", 6 | "timeZone": "London", 7 | "ntpServers": ["10.0.0.2, 10.0.0.3"], 8 | "dnsServers": ["10.0.0.2, 10.0.0.3"], 9 | "vcFQDNorIP": "vc.mydomain.com", 10 | "vcPort": 443, 11 | "vcUser": "idan@vsphere.local", 12 | "vcPassword": "MyExamplePwd!", 13 | "datacenter": "MyDC1", 14 | "esxCluster": "DC1_HA1", 15 | "ppdmIpV4": "10.0.0.10", 16 | "ppdmIpV4Netmask": "255.255.255.0", 17 | "ppdmIpv4Gateway": "10.0.0.1", 18 | "ppdmFQDN": "ppdm.mydomain.com", 19 | "ppdmAdminPwd": "MyExamplePPDMPwd!", 20 | "ppdmDatastore":"PMAX0123_0010_DS1", 21 | "ppdmMgmtNetwork": "VM Network", 22 | "_comment2": "Optionally provide full path to license file, defaults to trial license", 23 | "licenseFile": "C:\\Downloads\\MyLicense.xml" 24 | } -------------------------------------------------------------------------------- /PowerShell7/Example-01.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | # CONNECT THE THE REST API 13 | connect-dmapi -Server $Server 14 | 15 | $Filters = @( 16 | "name eq `"win-fsa-01`"" 17 | ) 18 | # GET ASSETS BASED ON FILTERS 19 | $Assets = get-dmassets -Filters $Filters -PageSize $PageSize 20 | 21 | # GET ALL ASSETS 22 | # $Assets = get-dmassets -PageSize $PageSize 23 | 24 | Write-Host "[$($Server)]: All disks for asset: $($Assets.name)" -ForegroundColor Yellow 25 | $Assets.details.vm.disks | sort-object label | Format-List 26 | 27 | Write-Host "[$($Server)]: We want to exclude with regex for asset: $($Assets.name)" -ForegroundColor Yellow 28 | $Assets.details.vm.disks | Where-Object {$_.label -notmatch "^Hard disk 1$"} | sort-object label 29 | 30 | # DISCONNECT FROM THE REST API 31 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-06.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $FolderName = "Recover" 14 | 15 | # CONNECT THE THE REST API 16 | connect-dmapi -Server $Server 17 | 18 | # GET THE vCenter 19 | $Filters = @( 20 | "viewType eq `"HOST`"" 21 | ) 22 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 23 | where-object {$_.name -eq "$($VMware)"} 24 | 25 | # GET THE DATACENTER 26 | $Filters = @( 27 | "viewType eq `"HOST`"", 28 | "and parentId eq `"$($vCenter.id)`"" 29 | ) 30 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 31 | where-object {$_.name -eq "$($DC)"} 32 | 33 | # GET A FOLDER 34 | $Filters = @( 35 | "viewType eq `"VM`"", 36 | "and parentId eq `"$($Datacenter.id)`"" 37 | ) 38 | $Folder = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 39 | where-object {$_.name -eq "$($FolderName)"} 40 | 41 | $Folder | format-list 42 | 43 | # DISCONNECT FROM THE REST API 44 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-07.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $ClusterName = "Cluster01" 14 | 15 | # CONNECT THE THE REST API 16 | connect-dmapi -Server $Server 17 | 18 | # GET THE vCenter 19 | $Filters = @( 20 | "viewType eq `"HOST`"" 21 | ) 22 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 23 | where-object {$_.name -eq "$($VMware)"} 24 | 25 | # GET THE DATACENTER 26 | $Filters = @( 27 | "viewType eq `"HOST`"", 28 | "and parentId eq `"$($vCenter.id)`"" 29 | ) 30 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 31 | where-object {$_.name -eq "$($DC)"} 32 | 33 | # GET A CLUSTER 34 | $Filters = @( 35 | "viewType eq `"HOST`"", 36 | "and parentId eq `"$($Datacenter.id)`"" 37 | 38 | ) 39 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 40 | where-object {$_.name -eq "$($ClusterName)"} 41 | 42 | $Cluster | format-list 43 | 44 | # DISCONNECT FROM THE REST API 45 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-02.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-04.vcorp.local" 10 | $PageSize = 100 11 | # $File = ".\myReport.csv" 12 | 13 | # CONNECT THE THE REST API 14 | connect-dmapi -Server $Server 15 | 16 | # GET ACTIVITIES BASED ON FILTERS 17 | $Date = (Get-Date).AddDays(-1) 18 | $Filters = @( 19 | "classType eq `"JOB`"" 20 | "and category eq `"PROTECT`"" 21 | "and startTime ge `"$($Date.ToString('yyyy-MM-dd'))T00:00:00.000Z`"", 22 | "and result.status eq `"FAILED`"" 23 | ) 24 | 25 | $Activities = get-dmactivities -Filters $Filters -PageSize $PageSize 26 | 27 | # GET ALL ACTIVITIES 28 | # $Activities = get-dmactivities -PageSize $PageSize 29 | 30 | $Activities | Select-Object ` 31 | @{n="Asset";e={$_.asset.name}}, 32 | @{n="Asset Source";e={$_.host.name}}, 33 | @{n="Status";e={$_.result.status}}, 34 | @{n="Precent Complete";e={$_.progress}}, 35 | @{n="Policy Name";e={$_.protectionPolicy.name}}, 36 | @{n="Job Type";e={$_.category}}, 37 | @{n="Asset Type";e={$_.asset.type}}, 38 | @{n="Start Time";e={$_.startTime}}, 39 | @{n="Activity Duration";e={$_.duration}}, 40 | @{n="Error Code";e={$_.result.error.code}} ` | 41 | Format-Table -AutoSize 42 | 43 | 44 | # DISCONNECT FROM THE REST API 45 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-08.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $ClusterName = "Cluster01" 14 | $RP = "Database" 15 | 16 | # CONNECT THE THE REST API 17 | connect-dmapi -Server $Server 18 | 19 | # GET THE vCenter 20 | $Filters = @( 21 | "viewType eq `"HOST`"" 22 | ) 23 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 24 | where-object {$_.name -eq "$($VMware)"} 25 | 26 | # GET THE DATACENTER 27 | $Filters = @( 28 | "viewType eq `"HOST`"", 29 | "and parentId eq `"$($vCenter.id)`"" 30 | ) 31 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 32 | where-object {$_.name -eq "$($DC)"} 33 | 34 | # GET A CLUSTER 35 | $Filters = @( 36 | "viewType eq `"HOST`"", 37 | "and parentId eq `"$($Datacenter.id)`"" 38 | 39 | ) 40 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 41 | where-object {$_.name -eq "$($ClusterName)"} 42 | 43 | # GET A RESOURCE POOL 44 | $Filters = @( 45 | "viewType eq `"HOST`"", 46 | "and parentId eq `"$($Cluster.id)`"" 47 | ) 48 | $Pool = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 49 | where-object {$_.name -eq "$($RP)"} 50 | 51 | $Pool | format-list 52 | 53 | # DISCONNECT FROM THE REST API 54 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-09.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $ClusterName = "Cluster01" 14 | $EsxName = "esx-physical-01.vcorp.local" 15 | 16 | # CONNECT THE THE REST API 17 | connect-dmapi -Server $Server 18 | 19 | # GET THE vCenter 20 | $Filters = @( 21 | "viewType eq `"HOST`"" 22 | ) 23 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 24 | where-object {$_.name -eq "$($VMware)"} 25 | 26 | # GET THE DATACENTER 27 | $Filters = @( 28 | "viewType eq `"HOST`"", 29 | "and parentId eq `"$($vCenter.id)`"" 30 | ) 31 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 32 | where-object {$_.name -eq "$($DC)"} 33 | 34 | # GET A CLUSTER 35 | $Filters = @( 36 | "viewType eq `"HOST`"", 37 | "and parentId eq `"$($Datacenter.id)`"" 38 | 39 | ) 40 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 41 | where-object {$_.name -eq "$($ClusterName)"} 42 | 43 | # GET AN ESX HOST 44 | $Filters = @( 45 | "viewType eq `"HOST`"", 46 | "and parentId eq `"$($Cluster.id)`"" 47 | ) 48 | $Esx = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 49 | where-object {$_.name -eq "$($EsxName)"} 50 | 51 | $Esx | format-list 52 | 53 | # DISCONNECT FROM THE REST API 54 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-10.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $ClusterName = "Cluster01" 14 | $EsxName = "esx-physical-01.vcorp.local" 15 | $DS = "Unity7496-DS-01" 16 | 17 | # CONNECT THE THE REST API 18 | connect-dmapi -Server $Server 19 | 20 | # GET THE vCenter 21 | $Filters = @( 22 | "viewType eq `"HOST`"" 23 | ) 24 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 25 | where-object {$_.name -eq "$($VMware)"} 26 | 27 | # GET THE DATACENTER 28 | $Filters = @( 29 | "viewType eq `"HOST`"", 30 | "and parentId eq `"$($vCenter.id)`"" 31 | ) 32 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 33 | where-object {$_.name -eq "$($DC)"} 34 | 35 | # GET A CLUSTER 36 | $Filters = @( 37 | "viewType eq `"HOST`"", 38 | "and parentId eq `"$($Datacenter.id)`"" 39 | 40 | ) 41 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 42 | where-object {$_.name -eq "$($ClusterName)"} 43 | 44 | # GET AN ESX HOST 45 | $Filters = @( 46 | "viewType eq `"HOST`"", 47 | "and parentId eq `"$($Cluster.id)`"" 48 | ) 49 | $Esx = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 50 | where-object {$_.name -eq "$($EsxName)"} 51 | 52 | $Datastores = get-dmesxdatastore ` 53 | -InventorySourceId $vCenter.inventorySourceId ` 54 | -HostSystemId $Esx.details.esxHost.attributes.esxHost.hostMoref | ` 55 | where-object {$_.name -eq "$($DS)"} 56 | 57 | $Datastores | format-list 58 | 59 | # DISCONNECT FROM THE REST API 60 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-22.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | #> 5 | 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | $Server = "ppdm-01.vcorp.local" 8 | $PageSize = 100 9 | $Config = @( 10 | @{ 11 | name = "vc1-win-01" 12 | disks = @( 13 | @{ 14 | label = "Hard disk 1" 15 | excluded = $false 16 | }, 17 | @{ 18 | label = "Hard disk 2" 19 | excluded = $true 20 | }, 21 | @{ 22 | label = "Hard disk 3" 23 | excluded = $false 24 | }, 25 | @{ 26 | label = "Hard disk 4" 27 | excluded = $false 28 | } 29 | ,@{ 30 | label = "Hard disk 5" 31 | excluded = $false 32 | } 33 | ) 34 | }, 35 | @{ 36 | name = "vc1-win-02" 37 | disks = @( 38 | @{ 39 | label = "Hard disk 1" 40 | excluded = $false 41 | }, 42 | @{ 43 | label = "Hard disk 2" 44 | excluded = $true 45 | }, 46 | @{ 47 | label = "Hard disk 3" 48 | excluded = $false 49 | }, 50 | @{ 51 | label = "Hard disk 4" 52 | excluded = $false 53 | } 54 | ,@{ 55 | label = "Hard disk 5" 56 | excluded = $false 57 | } 58 | ) 59 | } 60 | ) 61 | 62 | # CONNECT THE THE REST API 63 | connect-dmapi -Server $Server 64 | 65 | <# 66 | INCLUDE OR EXCLUDE ALL DISKS FOR VRTUAL MACHINE ASSETS EXCEPT: 67 | Hard disk 1 68 | #> 69 | 70 | $Config | foreach-object { 71 | $Filters = @( 72 | "name eq `"$($_.name)`"" 73 | ) 74 | $Asset = get-dmassets -Filters $Filters -PageSize $PageSize 75 | 76 | set-dmdiskexclusions -Asset $Asset -Config $_.disks 77 | 78 | } 79 | 80 | # DISCONNECT FROM THE REST API 81 | disconnect-dmapi -------------------------------------------------------------------------------- /Python/ppdm_deploy/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "PPDM Config", 3 | "ppdmOVALocation": "C:\\Downloads\\dellemc-ppdm-sw-19.14.0-20.ova", 4 | "ovfToolLocation": "ovftool", 5 | "ppdmVmName": "PPDM", 6 | "timeZone": "London", 7 | "ntpServers": ["10.0.0.2, 10.0.0.3"], 8 | "dnsServers": ["10.0.0.2, 10.0.0.3"], 9 | "vcFQDNorIP": "vc.mydomain.com", 10 | "vcPort": 443, 11 | "vcUser": "idan@vsphere.local", 12 | "vcPassword": "MyExamplePwd!", 13 | "_comment2": "vcNiceName is optional", 14 | "vcNiceName": "vc", 15 | "datacenter": "MyDC1", 16 | "esxCluster": "DC1_HA1", 17 | "ppdmIpV4": "10.0.0.10", 18 | "ppdmIpV4Netmask": "255.255.255.0", 19 | "ppdmIpv4Gateway": "10.0.0.1", 20 | "_comment3": "The following IPv6 params are optional:", 21 | "_comment4": "ppdmIpV6, ppdmIpV6Netmask and ppdmIpV6Gateway", 22 | "ppdmIpV6": "", 23 | "ppdmIpV6Netmask": "", 24 | "ppdmIpV6Gateway": "", 25 | "ppdmFQDN": "ppdm.mydomain.com", 26 | "ppdmAdminPwd": "MyExamplePPDMPwd!", 27 | "ppdmDatastore":"PMAX0123_0010_DS1", 28 | "ppdmMgmtNetwork": "VM Network", 29 | "_comment5": "Leave blank if PPDM would be deployed on vSphere on-prem", 30 | "_comment6": "Platform can be vmware, vmc, vcde, avs, gcve, ocvs", 31 | "platform": "vmware", 32 | "_comment7": "Optionally provide full path to license file, defaults to trial license", 33 | "licenseFile": "C:\\Downloads\\MyLicense.xml", 34 | "_comment8": "The following encryption settings are optional enabled by default", 35 | "protectionEncryption": true, 36 | "replicationEncryption": true, 37 | "_comment9": "The following PowerProtect DD parameters are optional:", 38 | "ddFQDNorIP": "10.0.0.9", 39 | "ddPort": 3009, 40 | "ddUser": "ddadminuser", 41 | "ddPassword": "MyExampleDDPwd!", 42 | "ddNiceName": "dd", 43 | "_comment10": "The following SMTP parameters are optional:", 44 | "smtpMailServer": "mailserver.nothing.com", 45 | "smtpMailFrom": "admin@nothing.com", 46 | "smtpPort": 25, 47 | "smtpUser": "idan", 48 | "smtpPassword":"MySmtpPwd", 49 | "autoSupport": true, 50 | "_comment11": "The following peer PPDM parameters are optional:", 51 | "peerPpdmFQDNorIP": "10.0.0.11", 52 | "peerPpdmPort": "8443", 53 | "peerPpdmUser": "admin", 54 | "peerPpdmPassword": "MyExamplePPDM2Pwd!", 55 | "peerPpdmNiceName": "PPDM2" 56 | } -------------------------------------------------------------------------------- /PowerShell7/Example-27.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | vMWare PowerCLI 6 | #> 7 | 8 | Import-Module .\dell.ppdm.psm1 -Force 9 | $vCenter = "vc-01.vcorp.local" 10 | $Regex = "^splunk-0[1|2|3]" 11 | 12 | $Server = "ppdm-01.vcorp.local" 13 | $PageSize = 100 14 | $assets = @() 15 | 16 | 17 | # IMPORT THE DEPLOYMENT CONFIGURATION 18 | $exists = test-path ".\$($vCenter).xml" -PathType Leaf 19 | if($exists) { 20 | $credential = Import-Clixml ".\$($vCenter).xml" 21 | # CONNECT TO VCENTER 22 | connect-viserver -Server $vCenter ` 23 | -Protocol https ` 24 | -Credential $credential 25 | 26 | # GET ALL VIRTUAL MACHINES 27 | $query = Get-View -ViewType VirtualMachine -Filter @{"name"="$($Regex)"} 28 | 29 | # FILTER MY LIST 30 | foreach ($vm in $query) { 31 | $disks = @() 32 | 33 | # GET THE DEVICES 34 | $devices = $vm.Config.Hardware.Device 35 | # FILTER THE DEVICES 36 | $harddisks = $devices | where-object {$_.DeviceInfo.Label -match "^Hard disk"} 37 | 38 | foreach($disk in $harddisks) { 39 | if( 40 | $disk.Backing.DiskMode ` 41 | -eq "independent_persistent" 42 | ) { 43 | $object = @{ 44 | key = $disk.key 45 | label = $disk.DeviceInfo.Label 46 | excluded = $true 47 | } 48 | } else { 49 | $object = @{ 50 | key = $disk.key 51 | label = $disk.DeviceInfo.Label 52 | excluded = $false 53 | } 54 | } 55 | 56 | $disks += (new-object -typename pscustomobject -property $object) 57 | } 58 | 59 | $object = [ordered]@{ 60 | name = $vm.name 61 | disks = $disks 62 | } 63 | 64 | $assets += (new-object -typename pscustomobject -property $object) 65 | 66 | } 67 | 68 | # DISCONNECT VCENTER 69 | disconnect-viserver -Force -Confirm:$false 70 | 71 | # CONNECT THE THE REST API 72 | connect-dmapi -Server $Server 73 | 74 | $assets | foreach-object { 75 | $Filters = @( 76 | "name eq `"$($_.name)`"" 77 | ) 78 | $Asset = get-dmassets -Filters $Filters -PageSize $PageSize 79 | 80 | set-dmdiskexclusions -Asset $Asset -Config $_.disks 81 | 82 | } # END FOREACH 83 | 84 | # DISCONNECT FROM THE REST API 85 | disconnect-dmapi 86 | 87 | } else { 88 | throw "Unable to find the credentials file for vCenter" 89 | } -------------------------------------------------------------------------------- /PowerShell7/Example-24.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $VMware = "vc-01.vcorp.local" 12 | $DC = "DC01-VC01" 13 | $ClusterName = "Cluster01" 14 | $EsxName = "esx-physical-01.vcorp.local" 15 | $DS = "Unity7496-DS-01" 16 | 17 | # SEARCH NODE 18 | $HostName = "ppdm-01-search-01.vcorp.local" 19 | $IpAddress = "192.168.3.53" 20 | $Dns = "192.168.1.11" 21 | $Gateway = "192.168.1.250" 22 | $Netmask = "255.255.252.0" 23 | 24 | # CONNECT THE THE REST API 25 | connect-dmapi -Server $Server 26 | 27 | # GET THE vCenter 28 | $Filters = @( 29 | "viewType eq `"HOST`"" 30 | ) 31 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 32 | where-object {$_.name -eq "$($VMware)"} 33 | 34 | # GET THE DATACENTER 35 | $Filters = @( 36 | "viewType eq `"HOST`"", 37 | "and parentId eq `"$($vCenter.id)`"" 38 | ) 39 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 40 | where-object {$_.name -eq "$($DC)"} 41 | 42 | # GET A CLUSTER 43 | $Filters = @( 44 | "viewType eq `"HOST`"", 45 | "and parentId eq `"$($Datacenter.id)`"" 46 | 47 | ) 48 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 49 | where-object {$_.name -eq "$($ClusterName)"} 50 | 51 | # GET AN ESX HOST 52 | $Filters = @( 53 | "viewType eq `"HOST`"", 54 | "and parentId eq `"$($Cluster.id)`"" 55 | ) 56 | $Esx = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 57 | where-object {$_.name -eq "$($EsxName)"} 58 | 59 | # PARSE OUT THE MOREF 60 | $EsxMoRef = ` 61 | $Esx.details.esxHost.attributes.esxHost.hostMoref ` 62 | -split ':' | select-object -last 1 63 | 64 | # PARSE OUT THE MOREF 65 | $NetMoRef = ` 66 | $Esx.details.esxHost.attributes.esxHost.networks.moref 67 | 68 | $Datastore = get-dmesxdatastore ` 69 | -InventorySourceId $vCenter.inventorySourceId ` 70 | -HostSystemId $Esx.details.esxHost.attributes.esxHost.hostMoref | ` 71 | where-object {$_.name -eq "$($DS)"} 72 | 73 | # PARSE OUT THE MOREF 74 | $DsMoRef = ($Datastore | select-object moref).moref -split ':' | ` 75 | select-object -last 1 76 | 77 | # GET THE SEARCH NODE ID 78 | 79 | $Body = @{ 80 | hostName = $HostName 81 | inventorySourceId = $vCenter.inventorySourceId 82 | deploymentConfig = [ordered]@{ 83 | fqdn = $HostName 84 | ipAddress = $IpAddress 85 | dns = $Dns 86 | gateway = $Gateway 87 | netMask = $Netmask 88 | networkMoref = $NetMoRef 89 | ipProtocol="IPv4" 90 | location = [ordered]@{ 91 | datastoreMoref = $DsMoRef 92 | hostMoref = $EsxMoRef 93 | } 94 | } 95 | additionalVMNetworks = @() 96 | } 97 | 98 | $SearchNode = new-dmsearchnode -Body $Body 99 | 100 | $SearchNode | format-list 101 | 102 | # DISCONNECT FROM THE REST API 103 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-13.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | Example-01.ps1 6 | #> 7 | Import-Module .\dell.ppdm.psm1 -Force 8 | 9 | # VARS 10 | $Server = "ppdm-01.vcorp.local" 11 | $PageSize = 100 12 | 13 | # CONNECT THE THE REST API 14 | connect-dmapi -Server $Server 15 | 16 | # GET ASSETS BASED ON FILTERS 17 | $Filters = @( 18 | "name eq `"ddve-01.vcorp.local`"" 19 | ) 20 | 21 | $Storage = get-dmstoragesystems -Filters $Filters -PageSize $PageSize 22 | 23 | # GUIDS FOR THE NEW POLICY AN STAGE 24 | $Guid1 = (New-Guid).guid 25 | $Guid2 = (New-Guid).guid 26 | 27 | # PREFERRED NETWROK INTERFACE 28 | $Preferred = $Storage.details.dataDomain.preferredInterfaces 29 | 30 | # CREATE THE REQUEST BODY 31 | $Body = [ordered]@{ 32 | name = "Policy-VM01" 33 | description = "Protect VMWare Virtual Machines" 34 | assetType = "VMWARE_VIRTUAL_MACHINE" 35 | type = "ACTIVE" 36 | encrypted = $false 37 | enabled = $true 38 | priority = 1 39 | dataConsistency = "CRASH_CONSISTENT" 40 | passive = $false 41 | forceFull = $false 42 | details = [ordered]@{ 43 | vm = @{ 44 | protectionEngine = "VMDIRECT" 45 | metadataIndexingEnabled = $true 46 | } 47 | } 48 | stages = @( 49 | [ordered]@{ 50 | id = $Guid1 51 | type = "PROTECTION" 52 | passive = $false 53 | attributes = [ordered]@{ 54 | vm = [ordered]@{ 55 | excludeSwapFiles = $false 56 | disableQuiescing = $true 57 | dataMoverType = "VADP" 58 | } 59 | protection = [ordered]@{ 60 | backupMode = "FSS" 61 | } 62 | } 63 | target = [ordered]@{ 64 | storageSystemId = $Storage.id 65 | dataTargetId = $null 66 | preferredInterface = $Preferred[1].networkAddress 67 | } 68 | slaId = $null 69 | sourceStageId = $null 70 | operations = @( 71 | [ordered]@{ 72 | id = $Guid2 73 | backupType = "SYNTHETIC_FULL" 74 | schedule = [ordered]@{ 75 | frequency = "DAILY" 76 | startTime = "2023-01-01T02:00:00.000Z" 77 | endTime = "2023-01-01T06:00:00.000Z" 78 | duration = "PT10H" 79 | interval = $null 80 | } 81 | } 82 | ) 83 | retention = [ordered]@{ 84 | unit = "DAY" 85 | storageSystemRetentionLock = $false 86 | interval = 5 87 | } 88 | extendedRetentions = @( 89 | [ordered]@{ 90 | selector = [ordered]@{ 91 | operationId = $Guid2 92 | backupType = "SYNTHETIC_FULL" 93 | } 94 | retention = [ordered]@{ 95 | unit = "DAY" 96 | storageSystemRetentionLock = $false 97 | interval = 5 98 | } 99 | } 100 | ) 101 | } 102 | ) 103 | filterIds = @() 104 | credentials = $null 105 | slaId = "" 106 | } 107 | 108 | # CREATE THE NEW PROTECTION POLICY 109 | $Policy = new-dmprotectionpolicy -Body $Body 110 | $Policy | format-list 111 | 112 | # DISCONNECT FROM THE REST API 113 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-20.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $vCenter = "vc-01.vcorp.local" 11 | $Datacenter = "DC01-VC01" 12 | $Cluster = "Cluster01" 13 | $EsxHost = "esx-physical-01.vcorp.local" 14 | $Datastore = "Unity7496-DS-01" 15 | 16 | $Proxy = "ppdm-01-proxy-01.vcorp.local" 17 | $Ip = "192.168.3.52" 18 | $Netmask = "255.255.252.0" 19 | $Gateway = "192.168.1.250" 20 | $Dns = "192.168.1.11" 21 | 22 | $PageSize = 100 23 | 24 | # CONNECT THE THE REST API 25 | connect-dmapi -Server $Server 26 | 27 | # GET THE vCenter 28 | $Filters = @( 29 | "viewType eq `"HOST`"" 30 | ) 31 | $Vc = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 32 | where-object {$_.name -eq "$($vCenter)"} 33 | 34 | # GET THE DATACENTER 35 | $Filters = @( 36 | "viewType eq `"HOST`"", 37 | "and parentId eq `"$($Vc.id)`"" 38 | ) 39 | $Dc = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 40 | where-object {$_.name -eq "$($Datacenter)"} 41 | 42 | # GET A CLUSTER 43 | $Filters = @( 44 | "viewType eq `"HOST`"", 45 | "and parentId eq `"$($Dc.id)`"" 46 | 47 | ) 48 | $Cls = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 49 | where-object {$_.name -eq "$($Cluster)"} 50 | 51 | # GET AN ESX HOST 52 | $Filters = @( 53 | "viewType eq `"HOST`"", 54 | "and parentId eq `"$($Cls.id)`"" 55 | ) 56 | $Esx = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 57 | where-object {$_.name -eq "$($EsxHost)"} 58 | 59 | # PARSE OUT THE MOREF 60 | $EsxMoRef = ` 61 | $Esx.details.esxHost.attributes.esxHost.hostMoref ` 62 | -split ':' | select-object -last 1 63 | 64 | # PARSE OUT THE MOREF 65 | $NetMoRef = ` 66 | $Esx.details.esxHost.attributes.esxHost.networks.moref 67 | 68 | # GET THE DATASTORE 69 | $Ds = get-dmesxdatastore ` 70 | -InventorySourceId $VC.inventorySourceId ` 71 | -HostSystemId $Esx.details.esxHost.attributes.esxHost.hostMoref | ` 72 | where-object {$_.name -eq $Datastore} 73 | # PARSE OUT THE MOREF 74 | $DsMoRef = ($Ds | where-object {$_.name -eq $Datastore}).moref -split ':' | ` 75 | select-object -last 1 76 | 77 | 78 | # GET THE PROTECTION ENGINE 79 | $Filters = @( 80 | "type eq `"VPE`"" 81 | ) 82 | $Pe = get-dmengines -Filters $Filters 83 | 84 | # DEPLOY THE PROTECTION ENGINE 85 | $Body = [ordered]@{ 86 | Config = [ordered]@{ 87 | ProxyType = "External" 88 | DeployProxy = $true 89 | Port = 9090 90 | Disabled = $false 91 | MORef = "" 92 | Credential = @{ 93 | Type = "ObjectId" 94 | } 95 | AdvancedOptions = @{ 96 | TransportSessions = [ordered]@{ 97 | Mode = "HotaddPreferred" 98 | UserDefined = $true 99 | } 100 | } 101 | SupportedProtectionTypes = @( 102 | "VM" 103 | ) 104 | ProxyDeploymentConfig = [ordered]@{ 105 | Location = [ordered]@{ 106 | HostMoref = $EsxMoRef 107 | DatastoreMoref = $DsMoRef 108 | NetworkMoref = $NetMoRef 109 | } 110 | Timezone = "" 111 | AdditionalVMNetworks = @() 112 | Fqdn = $Proxy 113 | IpAddress = $Ip 114 | NetMask = $NetMask 115 | Gateway = $Gateway 116 | PrimaryDns = $Dns 117 | Dns = $Dns 118 | IPProtocol = "IPv4" 119 | } 120 | VimServerRef = [ordered]@{ 121 | Type ="ObjectId" 122 | ObjectId = $Esx.inventorySourceId 123 | } 124 | } 125 | } 126 | 127 | $Engine = new-dmengine -Id $Pe.id -Body $Body 128 | 129 | $Activity = $Engine._links.task.href -split '/' | select-object -last 1 130 | 131 | new-dmmonitor -ActivityId $activity -Poll 15 132 | 133 | # DISCONNECT FROM THE REST API 134 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/Example-26.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | $Poll = 15 12 | $Recover = @() 13 | $SourceDatabase = "data_warehouse_s03" 14 | $SourceSqlHost = "win-sql-03.vcorp.local" 15 | 16 | $TargetSqlHosts = @( 17 | <# 18 | @{ 19 | name="win-sql-02.vcorp.local" 20 | dbAltName = "test_restore_s02" 21 | dbAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 22 | logAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 23 | forceDatabaseOverwrite = $false 24 | enableDebug = $false 25 | enableCompressedRestore = $false 26 | disconnectDatabaseUsers = $false 27 | }, 28 | @{ 29 | name="win-sql-03.vcorp.local" 30 | dbAltName = "test_restore_s03" 31 | dbAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 32 | logAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 33 | forceDatabaseOverwrite = $false 34 | enableDebug = $false 35 | enableCompressedRestore = $false 36 | disconnectDatabaseUsers = $false 37 | }, 38 | #> 39 | @{ 40 | name="win-sql-04.vcorp.local" 41 | dbAltName = "test_restore_s04" 42 | dbAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 43 | logAltPath = "C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA" 44 | forceDatabaseOverwrite = $false 45 | enableDebug = $false 46 | enableCompressedRestore = $false 47 | disconnectDatabaseUsers = $false 48 | } 49 | ) 50 | 51 | # CONNECT THE THE REST API 52 | connect-dmapi -Server $Server 53 | 54 | # GET ASSETS BASED ON FILTERS 55 | $Filters = @( 56 | "name eq `"$($SourceDatabase)`"" 57 | "and details.database.clusterName eq `"$($SourceSqlHost)`"" 58 | ) 59 | 60 | $Asset = get-dmassets -Filters $Filters -PageSize $PageSize 61 | 62 | # GET THE LATEST FULL COPY 63 | $Filters = @( 64 | "assetId in (`"$($Asset.id)`")", 65 | "and copyType in (`"FULL`")", 66 | "and replicatedCopy eq false", 67 | "and location in (`"LOCAL`", `"LOCAL_RECALLED`")", 68 | "and not state in (`"DELETED`", `"DELETING`", `"SOFT_DELETED`", `"DELETE_FAILED`")" 69 | ) 70 | 71 | $LatestCopy = get-dmlatestcopies -Filters $Filters -PageSize $PageSize 72 | 73 | # ITERATE OVER THE DEFINED SQL HOSTS 74 | $TargetSqlHosts | foreach-object { 75 | $Filters = @( 76 | "name eq `"$($_.name)`"" 77 | "and attributes.appHost.protectionEngineFlow eq `"APPDIRECT`"", 78 | "and not (lastDiscoveryStatus eq `"DELETED`")", 79 | "and attributes.appHost.os eq `"WINDOWS`"" 80 | ) 81 | $Node = get-dminfrastructurenodes ` 82 | -Filters $Filters ` 83 | -Type MICROSOFT_SQL_DATABASE_VIEW ` 84 | -PageSize $PageSize 85 | 86 | $Filters = @( 87 | "clusterType in (`"NONE`", `"FCI`")", 88 | "and lastDiscoveryStatus eq `"NEW`"" 89 | ) 90 | 91 | $AppSys = get-dminfrastructurenodeschildren ` 92 | -Id $Node.id ` 93 | -Filters $Filters ` 94 | -Type MICROSOFT_SQL_DATABASE_VIEW ` 95 | -PageSize $PageSize 96 | 97 | $Body = [ordered]@{ 98 | restoreType = "TO_ALTERNATE" 99 | description = "Restore to alternate location custom location" 100 | copyIds = [array]$LatestCopy.id 101 | restoredCopiesDetails = @{ 102 | targetDatabaseInfo = [ordered]@{ 103 | hostId = $Node.details.host.id 104 | applicationSystemId = $AppSys.details.appServer.id 105 | assetName = $_.dbAltName 106 | } 107 | } 108 | options= [ordered]@{ 109 | forceDatabaseOverwrite = $_.forceDatabaseOverwrite 110 | enableDebug = $_.enableDebug 111 | recoveryState = "RECOVERY" 112 | enableCompressedRestore = $_.enableCompressedRestore 113 | disconnectDatabaseUsers = $_.disconnectDatabaseUsers 114 | fileRelocationOptions = [ordered]@{ 115 | type="CUSTOM_LOCATION" 116 | targetDataFileLocation = $_.dbAltPath 117 | targetLogFileLocation = $_.logAltPath 118 | } 119 | } 120 | } 121 | # KICK OFF THE RECOVERY AND GRAB YHE ACTIVITY ID 122 | $Recover += new-dmrecover -Body $Body 123 | 124 | } 125 | # MONITOR THE ACTIVITIES 126 | $Recover | Foreach-Object { 127 | new-dmmonitor -ActivityId $_.activityId -Poll $Poll 128 | } 129 | 130 | # DISCONNECT FROM THE REST API 131 | disconnect-dmapi -------------------------------------------------------------------------------- /PowerShell7/README.md: -------------------------------------------------------------------------------- 1 | # PowerShell7 2 | # Authors 3 | Cliff Rodriguez 4 | * [Dell Technoligies](https://www.dell.com/en-us) 5 | * [LinkedIn](https://www.linkedin.com/in/cliff-rodriguez-6673422b/) 6 | # Supported Platforms 7 | * PowerProtect Data Manager 19.16 8 | # Prerequisites 9 | * PowerShell 7.(latest) - [github](https://github.com/PowerShell/powershell/releases) 10 | # Conventions 11 | * [CMDLET Guidelines](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines?view=powershell-7.3) 12 | * CMDLET names are in lower case 13 | * CMDLET names begin with a PowerShell [approved verb](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.3) 14 | * CMDLET nouns are prefixed with dm to avoid any naming convention collisions 15 | * CMDLET variables are in pascal case 16 | * CMDLET bindings must be used outside of: 17 | * $global:ApiVersion 18 | * $global:AuthObject 19 | * $global:Port 20 | * CMDLET help must be defined 21 | * List module functions 22 | * PS> Import-Module .\dell.ppdm.psm1 -Force 23 | * PS> Get-Command -Module dell.ppdm 24 | * List cmdlet help after module is imported (basic, detailed, verbose w/ examples) 25 | * PS> {cmdlet-name} -? 26 | * PS> Get-Help -Name {cmdlet-name} -Detailed 27 | * PS> Get-Help -Name {cmdlet-name} -Full 28 | # Documentation 29 | * PowerProtect Data Manager - [rest api](https://developer.dell.com/apis/4378/versions/19.16.0/docs/introduction.md) 30 | * PowerProtect Data Manager - [info hub](https://www.dell.com/support/kbdoc/en-us/000196987/dell-powerprotect-data-manager-info-hub-product-documents-and-information?lang=en) 31 | * PowerShell 7.(latest) - [docs](https://learn.microsoft.com/en-us/powershell) 32 | 33 | 34 | # Examples 35 | | Name | Description | Environment | Category | 36 | |:-----------:|:-------------------------------------------------------------------------|:-----------:|:--------:| 37 | | Example-01 | Get assets based on filters | ANY | Query | 38 | | Example-02 | Get activities based on filters | ANY | Query | 39 | | Example-03 | Get alerts based on filters | ANY | Query | 40 | | Example-04 | Get the attached vMware vCenter | VMWARE | Query | 41 | | Example-05 | Get the datacenters for the defined vCenter | VMWARE | Query | 42 | | Example-06 | Get a folder within the defined vCenter\datacenter | VMWARE | Query | 43 | | Example-07 | Get a cluster within the defined vCenter\datacenter | VMWARE | Query | 44 | | Example-08 | Get a resource pool within the defined vCenter\datacenter\cluster | VMWARE | Query | 45 | | Example-09 | Get an esx host within the defined vCenter\datacenter\cluster | VMWARE | Query | 46 | | Example-10 | Get the datastores attached to vCenter\datacenter\cluster\esx host | VMWARE | Query | 47 | | Example-11 | Start an ad hoc virtual machine policy based backup | VMWARE | Backup | 48 | | Example-12 | Start an ad hoc virtual machine client based backup | VMWARE | Backup | 49 | | Example-13 | Create a virtual machine protection policy | ANY | Config | 50 | | Example-14 | Get the latest copy for an asset | ANY | Query | 51 | | Example-15 | Get attached PowerProtect DD storage systems | ANY | Query | 52 | | Example-16 | Recover an asset from the latest copy via instant access then vmotion | VMWARE | Recover | 53 | | Example-17 | Get protection policies based on filters | ANY | Query | 54 | | Example-18 | Get agents registered with the system | ANY | Query | 55 | | Example-19 | Get a certificate from 3rd party application | ANY | Query | 56 | | Example-20 | Deploy a protection engine | VMWARE | Config | 57 | | Example-21 | Get credentials based on filters | ANY | Query | 58 | | Example-22 | Set custom disk exclusions per asset | VMWARE | Config | 59 | | Example-23 | Create new credentials | ANY | Config | 60 | | Example-24 | Deploy a search node | VMWARE | Config | 61 | | Example-25 | Get the audit logs based on filters | ANY | Query | 62 | | Example-26 | Recover database asset to multiple SQL hosts using custom name & paths | ANY | Recover | 63 | | Example-27 | Set custom disk exclusions per asset by disk mode (advanced) | VMWARE | Config | 64 | | Example-28 | Cancels a running or queued activity | ANY | Backup | -------------------------------------------------------------------------------- /PowerShell7/Example-16.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | Import-Module .\dell.ppdm.psm1 -Force 7 | 8 | # VARS 9 | $Server = "ppdm-01.vcorp.local" 10 | $PageSize = 100 11 | 12 | $VmName = "win-iis-01" 13 | $VcenterName = "vc-01.vcorp.local" 14 | $DcName = "DC01-VC01" 15 | $FolderName = "Recover" 16 | $ClusterName = "Cluster01" 17 | $PoolName = "Web" 18 | $EsxName = "esx-physical-01.vcorp.local" 19 | $DsName = "Unity7496-DS-01" 20 | 21 | # CONNECT THE THE REST API 22 | connect-dmapi -Server $Server 23 | 24 | $Filters = @( 25 | "name eq `"$($VmName)`"" 26 | ) 27 | # GET ASSETS BASED ON FILTERS 28 | $Asset = get-dmassets -Filters $Filters -PageSize $PageSize 29 | $AssetMoRef = $Asset.details.vm.vmMoref -split ':' | select-object -last 1 30 | 31 | # GET THE LATEST COPY 32 | $Filters = @( 33 | "assetId in (`"$($Asset.id)`")" 34 | ) 35 | $Copy = get-dmlatestcopies -Filters $Filters -PageSize 100 36 | 37 | # GET THE vCenter 38 | $Filters = @( 39 | "viewType eq `"HOST`"" 40 | ) 41 | $vCenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 42 | where-object {$_.name -eq "$($VcenterName)"} 43 | 44 | # GET THE DATACENTER 45 | $Filters = @( 46 | "viewType eq `"HOST`"", 47 | "and parentId eq `"$($vCenter.id)`"" 48 | ) 49 | $Datacenter = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 50 | where-object {$_.name -eq "$($DcName)"} 51 | $DcMoRef = $Datacenter.id -split ':' | select-object -last 1 52 | 53 | # GET A FOLDER 54 | $Filters = @( 55 | "viewType eq `"VM`"", 56 | "and parentId eq `"$($Datacenter.id)`"" 57 | ) 58 | $Folder = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 59 | where-object {$_.name -eq "$($FolderName)"} 60 | $FolderMoRef = $Folder.id -split ':' | select-object -last 1 61 | 62 | # GET A CLUSTER 63 | $Filters = @( 64 | "viewType eq `"HOST`"", 65 | "and parentId eq `"$($Datacenter.id)`"" 66 | 67 | ) 68 | $Cluster = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 69 | where-object {$_.name -eq "$($ClusterName)"} 70 | $ClusterMoRef = $Cluster.id -split ':' | select-object -last 1 71 | 72 | # GET A RESOURCE POOL 73 | $Filters = @( 74 | "viewType eq `"HOST`"", 75 | "and parentId eq `"$($Cluster.id)`"" 76 | ) 77 | $Pool = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 78 | where-object {$_.name -eq "$($PoolName)"} 79 | $PoolMoRef = $Pool.id -split ':' | select-object -last 1 80 | 81 | # GET AN ESX HOST 82 | $Filters = @( 83 | "viewType eq `"HOST`"", 84 | "and parentId eq `"$($Cluster.id)`"" 85 | ) 86 | $Esx = get-dmvirtualcontainers -Filters $Filters -PageSize $PageSize | ` 87 | where-object {$_.name -eq "$($EsxName)"} 88 | $EsxMoRef = $Esx.id -split ':' | select-object -last 1 89 | $NetMoref = $Esx.details.esxHost.attributes.esxHost.networks[0].moref 90 | 91 | $Datastores = get-dmesxdatastore ` 92 | -InventorySourceId $vCenter.inventorySourceId ` 93 | -HostSystemId $Esx.details.esxHost.attributes.esxHost.hostMoref | ` 94 | where-object {$_.name -eq "$($DsName)"} 95 | $DsMoRef = $Datastores.moref -split ':' | select-object -last 1 96 | 97 | 98 | # BUILD THE JSON REQUEST BODY 99 | $Body = [ordered]@{ 100 | description = "DR_$($Asset.name) instant access recovery" 101 | copyIds = @("$($Copy.id)") 102 | restoreType = "INSTANT_ACCESS" 103 | options = @{ 104 | enableCompressedRestore = $false 105 | } 106 | restoredCopiesDetails = [ordered]@{ 107 | targetVmInfo = [ordered]@{ 108 | inventorySourceId = "$($Esx.inventorySourceId)" 109 | vmName = "DR_$($Asset.name)" 110 | dataCenterMoref = "$($DcMoRef)" 111 | hostMoref = "$($EsxMoRef)" 112 | dataStoreMoref = "$($DsMoRef)" 113 | clusterMoref = "$($ClusterMoRef)" 114 | folderMoref = "$($FolderMoRef)" 115 | resourcePoolMoref = "$($PoolMoRef)" 116 | disks = @() 117 | vmPowerOn = $true 118 | vmReconnectNic = $false 119 | tagRestoreDirective = "OFF" 120 | spbmRestoreDirective = "OFF" 121 | networks = @( 122 | [ordered]@{ 123 | networkLabel = "Network adapter 1" 124 | networkMoref = "$($NetMoref)" 125 | networkName = "VM Network" 126 | reconnectNic = $true 127 | } 128 | ) 129 | recoverConfig = $true 130 | } 131 | } 132 | } #END BODY 133 | 134 | $Recover = new-dmrecover -Body $Body 135 | 136 | $Monitor = new-dmmonitor -ActivityId $Recover.activityId -Poll 15 137 | 138 | # GET THE INSTANT ACCESS SESSION 139 | $Filters = @( 140 | "copyId eq `"$($Copy.id)`"", 141 | "and exportType ne `"RESTORED_COPIES`"" 142 | "and dataSourceSubType eq `"VIRTUALMACHINE`"" 143 | ) 144 | $Ia = get-dmexportedcopies -Filters $Filters 145 | 146 | $Body = [ordered]@{ 147 | description = "Relocate virtual machine for DR_$($Asset.name)" 148 | copyId = "$($Copy.id)" 149 | vmMoref = "$($AssetMoRef)" 150 | targetDatastoreMoref = "$($DsMoRef)" 151 | disks = @() 152 | } 153 | $Vmotion = new-dmvmotion -Ia $Ia.restoredCopyId -Body $Body 154 | 155 | $Vmotion | format-list 156 | 157 | # DISCONNECT FROM THE REST API 158 | disconnect-dmapi -------------------------------------------------------------------------------- /Python/certmgmt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | import requests 6 | import urllib3 7 | 8 | # This script simplifies certificate management in PowerProtect DAta Manager 9 | # Author - Idan Kentor 10 | # Copyright [2025] [Idan Kentor] 11 | 12 | # Script to simplify certificate management in PowerProtect Data Manager 13 | # Examples: 14 | # python certmgmt.py -s 10.0.0.1 -usr admin -pwd "myPassword!" -a list 15 | # python certmgmt.py -s 10.0.0.1 -usr admin -pwd "myPassword!" -a accept -id MTAuMjQ3LjUuNDU6MzAwOTpo4n8G 16 | # python certmgmt.py -s 10.0.0.1 -usr admin -pwd "myPassword!" -a accept -host api.idan.openshift-example.com 17 | 18 | urllib3.disable_warnings() 19 | 20 | 21 | def get_args(): 22 | """Get command line args from the user""" 23 | parser = argparse.ArgumentParser( 24 | description='Script to simplify certificate management in PowerProtect Data Manager') 25 | parser.add_argument('-s', '--server', required=True, 26 | action='store', help='PPDM DNS name or IP') 27 | parser.add_argument('-usr', '--user', required=False, action='store', 28 | default='admin', help='User') 29 | parser.add_argument('-pwd', '--password', required=True, action='store', 30 | help='Password') 31 | parser.add_argument('-a', '--action', required=True, choices=['list', 'accept'], 32 | help='Choose to list certificates or accept a specific one') 33 | parser.add_argument('-id', '--id', required=False, action='store', default=None, 34 | help='Optionally provide the ID the certificate to accept') 35 | parser.add_argument('-cert-host', '--certhost', required=False, action='store', default=None, 36 | help='Optionally provide the certificate host to accept') 37 | args = parser.parse_args() 38 | return args 39 | 40 | 41 | def init_rest_call(verb, uri, token, payload=None, params=None): 42 | """Generic function for REST calls""" 43 | if uri.endswith("/login"): 44 | headers = {"Content-Type": "application/json"} 45 | else: 46 | headers = { 47 | "Content-Type": "application/json", 48 | "Authorization": "Bearer " f"{token}", 49 | } 50 | payload = json.dumps(payload) 51 | verify = False 52 | timeout = 90 53 | try: 54 | if verb.lower() == "get": 55 | response = requests.get( 56 | uri, 57 | headers=headers, 58 | params=params, 59 | verify=verify, 60 | timeout=timeout 61 | ) 62 | else: 63 | response = requests.request( 64 | verb, 65 | uri, 66 | headers=headers, 67 | params=params, 68 | data=payload, 69 | verify=verify, 70 | timeout=timeout, 71 | ) 72 | response.raise_for_status() 73 | except requests.exceptions.ConnectionError as error: 74 | print(f"-> Error Connecting to {uri}: {error}") 75 | raise SystemExit(1) from error 76 | except requests.exceptions.Timeout as error: 77 | print(f"-> Connection timed out {urllib3}: {error}") 78 | raise SystemExit(1) from error 79 | except requests.exceptions.RequestException as error: 80 | if response.status_code in (401, 502): 81 | return False 82 | print( 83 | f"-> The call {response.request.method} {response.url} \ 84 | failed with exception:{error}" 85 | ) 86 | if not response.content: 87 | return True 88 | if uri.endswith("/login"): 89 | return response.json()["access_token"] 90 | try: 91 | return response.json() 92 | except (AttributeError, ValueError): 93 | return response.content 94 | 95 | 96 | def authenticate(uri, user, password): 97 | """Logins into PowerProtect Data Manager""" 98 | uri = f"{uri}/login" 99 | payload = {"username": user, "password": password} 100 | token = init_rest_call("POST", uri, payload, payload) 101 | return token 102 | 103 | 104 | def get_certs(uri, token, cert_id=None, cert_host=None): 105 | """Retrieves a list of credentials""" 106 | uri = f"{uri}/certificates" 107 | query_params = None 108 | if cert_id: 109 | uri = f"{uri}/{cert_id}" 110 | if cert_host: 111 | query = f'host eq "{cert_host}"' 112 | query_params = {'filter': query} 113 | response = init_rest_call("GET", uri, token, None, query_params) 114 | return response 115 | 116 | 117 | def accept_cert(uri, token, certs): 118 | """Performs removal of credentials by ID""" 119 | uri = f"{uri}/certificates/{certs["id"]}" 120 | response = init_rest_call("PUT", uri, token, payload=certs) 121 | return response 122 | 123 | 124 | def main(): 125 | port = "8443" 126 | apiendpoint = "/api/v2" 127 | args = get_args() 128 | ppdm, user, password = args.server, args.user, args.password 129 | action, cert_id, cert_host = args.action, args.id, args.certhost 130 | uri = f"https://{ppdm}:{port}{apiendpoint}" 131 | token = authenticate(uri, user, password) 132 | if action == "list": 133 | certs = get_certs(uri, token) 134 | print(json.dumps(certs, indent=4)) 135 | elif action == "accept": 136 | certs = None 137 | if not cert_host and not cert_id: 138 | print("Specify either certificate host or ID. Exiting...") 139 | raise SystemExit(1) 140 | if cert_host: 141 | if not cert_id: 142 | certs = get_certs(uri, token, None, cert_host) 143 | if not certs: 144 | print(f"Could not obtain certificate ID by host {cert_host}. Exiting...") 145 | raise SystemExit(1) 146 | if "content" in certs: 147 | if len(certs["content"]) > 1: 148 | print(f"Certificate host {cert_host} yielded in more than 1 result. Exiting...") 149 | raise SystemExit(1) 150 | if len(certs["content"]) == 0: 151 | print(f"Certificate host {cert_host} Could not be found. Exiting...") 152 | raise SystemExit(1) 153 | certs = certs["content"][0] 154 | cert_id = certs["id"] 155 | if cert_id: 156 | if not certs: 157 | certs = get_certs(uri, token, cert_id) 158 | certs["state"] = "ACCEPTED" 159 | result = accept_cert(uri, token, certs) 160 | if result: 161 | print(f"Certificate ID {certs["id"]} of host {certs["host"]} accepted successfully") 162 | else: 163 | print(f"Certificate ID {certs["id"]} of host {certs["host"]} could not be accepted") 164 | 165 | 166 | if __name__ == "__main__": 167 | main() 168 | -------------------------------------------------------------------------------- /Python/secure_login_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os.path 5 | import json 6 | import requests 7 | import urllib3 8 | from cryptography.fernet import Fernet 9 | 10 | # This script purpose is to assist with PowerProtect Data Manager secure login 11 | # Author - Idan Kentor 12 | # Version 1 - May 2024 13 | # Copyright [2024] [Idan Kentor] 14 | 15 | # Examples: 16 | # python secure_login_helper.py --secure-file-path c:\my_vault --ppdm 10.0.0.1 17 | # python secure_login_helper.py --password MyTestPwd! --secure-file-path c:\my_vault --ppdm 10.0.0.1 18 | # python secure_login_helper.py --password MyTestPwd! --no-secure-password-file 19 | # python secure_login_helper.py --clear-password-file --secure-file-path c:\repo 20 | 21 | 22 | urllib3.disable_warnings() 23 | 24 | 25 | def get_args(): 26 | """Gets command line args from the user""" 27 | parser = argparse.ArgumentParser( 28 | description="Facilitate PPDM credentials management for REST API auth" 29 | ) 30 | parser.add_argument( 31 | "-securefilepath", 32 | "--secure-file-path", 33 | required=False, 34 | dest="file_path", 35 | action="store", 36 | help="Provide the directory of the secure files", 37 | ) 38 | parser.add_argument( 39 | "-pass", 40 | "--password", 41 | required=False, 42 | dest="password", 43 | action="store", 44 | help="Specify clear text password", 45 | ) 46 | parser.add_argument( 47 | "-clear-file", 48 | "--clear-password-file", 49 | required=False, 50 | dest="clear_password_file", 51 | action="store", 52 | help="full path to clear text password file", 53 | ) 54 | parser.add_argument( 55 | "-nosecurepassfile", 56 | "--no-secure-password-file", 57 | required=False, 58 | dest="no_secure_file", 59 | action="store_true", 60 | help="Optionally bypass secure password file creation", 61 | ) 62 | parser.add_argument( 63 | "-ppdm", 64 | "--ppdm", 65 | required=False, 66 | dest="ppdm", 67 | action="store", 68 | help="PPDM server FQDN or IP", 69 | ) 70 | parser.add_argument( 71 | "-u", 72 | "--username", 73 | required=False, 74 | dest="username", 75 | action="store", 76 | default="admin", 77 | help="Optionally provide the PPDM username", 78 | ) 79 | args = parser.parse_args() 80 | return args 81 | 82 | 83 | def encrypt_pwd(password): 84 | """Encrypts password using Fernet from clear text""" 85 | key = Fernet.generate_key() 86 | fernet_key = Fernet(key) 87 | password_bytes = bytes(password, "utf-8") 88 | encrypted_password_bytes = fernet_key.encrypt(password_bytes) 89 | encrypted_password = encrypted_password_bytes.decode("utf-8") 90 | key = key.decode("utf-8") 91 | return encrypted_password, key 92 | 93 | 94 | def decrypt_pwd(encrypted_password, key): 95 | """Decrypts password using encrypted password and key""" 96 | encrypted_password_bytes = bytes(encrypted_password, "utf-8") 97 | key_bytes = bytes(key, "utf-8") 98 | fernet_key = Fernet(key_bytes) 99 | password_bytes = fernet_key.decrypt(encrypted_password_bytes) 100 | password = password_bytes.decode("utf-8") 101 | return password 102 | 103 | 104 | def authenticate(ppdm, uri, user, password): 105 | """Logins to PowerProtect Data Manager""" 106 | suffixurl = "/login" 107 | verify = False 108 | timeout = 90 109 | uri += suffixurl 110 | headers = {'Content-Type': 'application/json'} 111 | payload = {"username": user, "password": password} 112 | payload = json.dumps(payload) 113 | try: 114 | response = requests.post( 115 | uri, 116 | data=payload, 117 | headers=headers, 118 | verify=verify, 119 | timeout=timeout 120 | ) 121 | response.raise_for_status() 122 | except requests.exceptions.ConnectionError as err: 123 | print(f"Error Connecting to {ppdm}: {err}") 124 | raise SystemExit(1) from err 125 | except requests.exceptions.Timeout as err: 126 | print(f"Connection timed out {ppdm}: {err}") 127 | raise SystemExit(1) from err 128 | except requests.exceptions.RequestException as err: 129 | print(f"The call {response.request.method} {response.url} failed with exception:{err}") 130 | raise SystemExit(1) from err 131 | if response.status_code != 200: 132 | raise SystemExit(f"Login failed for user: {user}, code: {response.status_code}, body: {response.text}") 133 | print(f"Login for user: {user} to PPDM: {ppdm}") 134 | token = response.json()["access_token"] 135 | return token 136 | 137 | 138 | def read_file(secure_file_path, file): 139 | """Reads secure files""" 140 | file_path = os.path.join(secure_file_path, file) 141 | with open(file_path, "r", encoding="utf-8") as file_handle: 142 | item = ''.join(file_handle.readlines()) 143 | file_handle.close() 144 | return item 145 | 146 | 147 | def write_file(secure_file_path, file, data): 148 | """Writes encrypted password and key to files in the secure path""" 149 | file_path = os.path.join(secure_file_path, file) 150 | with open(file_path, "wb") as file_handle: 151 | data_bytes = bytes(data, "utf-8") 152 | file_handle.write(data_bytes) 153 | file_handle.close() 154 | 155 | 156 | def main(): 157 | args = get_args() 158 | password, clear_password_file = args.password, args.clear_password_file 159 | no_secure_file, secure_file_path = args.no_secure_file, args.file_path 160 | username, ppdm = args.username, args.ppdm 161 | password_file_name = "pwd" 162 | key_file_name = "key" 163 | api_port = 8443 164 | api_endpoint = "/api/v2" 165 | uri = f"https://{ppdm}:{api_port}{api_endpoint}" 166 | if secure_file_path: 167 | if password or clear_password_file: 168 | print("-> Both password and file provided. Using secure password file") 169 | encrypted_password = read_file(secure_file_path, password_file_name) 170 | key = read_file(secure_file_path, key_file_name) 171 | password = decrypt_pwd(encrypted_password, key) 172 | else: 173 | if password and clear_password_file: 174 | print("-> Both password and file provided. Using provided password") 175 | if not password and clear_password_file: 176 | password = read_file(args.secure_file_path, password_file_name) 177 | if not password and not clear_password_file: 178 | raise SystemExit("-> No password specified. Exiting") 179 | if not no_secure_file: 180 | encrypted_password, key = encrypt_pwd(password) 181 | print("Writing password and key files to directory:", secure_file_path) 182 | write_file(secure_file_path, password_file_name, encrypted_password) 183 | write_file(secure_file_path, key_file_name, key) 184 | if args.ppdm: 185 | token = authenticate(ppdm, uri, username, password) 186 | print("PPDM access token:") 187 | print(token) 188 | else: 189 | print("No PowerProtect Data Manager host specified. Exiting") 190 | 191 | 192 | if __name__ == "__main__": 193 | main() 194 | -------------------------------------------------------------------------------- /Python/assetmgmt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import urllib3 6 | import sys 7 | import json 8 | import time 9 | 10 | # The purpose of this script is to facilitate asset management in PowerProtect 11 | 12 | urllib3.disable_warnings() 13 | 14 | def get_args(): 15 | # Get command line args from the user 16 | parser = argparse.ArgumentParser( 17 | description='Script to manage Assets in PowerProtect Data Manager') 18 | parser.add_argument('-s', '--server', required=True, 19 | action='store', help='PPDM DNS name or IP') 20 | parser.add_argument('-usr', '--user', required=False, action='store', 21 | default='admin', help='User') 22 | parser.add_argument('-pwd', '--password', required=True, action='store', 23 | help='Password') 24 | parser.add_argument('-n', '--name', required=False, action='store', 25 | help='Optionally provide the name of the inventory source to discover') 26 | parser.add_argument('-t', '--type', required=False, action='store', default='vCenter', 27 | help='Optionally provide the type of inventory source to discover') 28 | args = parser.parse_args() 29 | return args 30 | 31 | def authenticate(ppdm, user, password, uri): 32 | # Login 33 | suffixurl = "/login" 34 | uri += suffixurl 35 | headers = {'Content-Type': 'application/json'} 36 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 37 | try: 38 | response = requests.post(uri, data=payload, headers=headers, verify=False) 39 | response.raise_for_status() 40 | except requests.exceptions.ConnectionError as err: 41 | print('Error Connecting to {}: {}'.format(ppdm, err)) 42 | sys.exit(1) 43 | except requests.exceptions.Timeout as err: 44 | print('Connection timed out {}: {}'.format(ppdm, err)) 45 | sys.exit(1) 46 | except requests.exceptions.RequestException as err: 47 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 48 | sys.exit(1) 49 | if (response.status_code != 200): 50 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 51 | user, response.status_code, response.text)) 52 | print('Login for user: {} to PPDM: {}'.format(user, ppdm)) 53 | token = response.json()['access_token'] 54 | return token 55 | 56 | def get_inventory_src(ppdm, uri, token, name, invtype): 57 | # Get Inventory Source 58 | suffixurl = "/inventory-sources" 59 | uri += suffixurl 60 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 61 | if invtype: 62 | if invtype.lower() in ["vcenter", "vc"]: 63 | filter = 'type eq "VCENTER"' 64 | elif invtype.lower() in ["k8s", "kubernetes"]: 65 | filter = 'type eq "KUBERNETES"' 66 | elif invtype.lower() in ["dd", "datadomain", "data domain"]: 67 | filter = 'type eq "EXTERNALDATADOMAIN"' 68 | elif invtype.lower() == ["app", "appgroup"]: 69 | filter = 'type eq "DEFAULTAPPGROUP"' 70 | if name: 71 | filter += ' and name lk "%{}%"'.format(name) 72 | params = {'filter': filter} 73 | try: 74 | response = requests.get(uri, headers=headers, params=params, verify=False) 75 | response.raise_for_status() 76 | except requests.exceptions.RequestException as err: 77 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 78 | if (response.status_code != 200): 79 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 80 | uri, response.status_code, response.text)) 81 | return response.json()['content'] 82 | 83 | def discover_inventory_src(ppdm, uri, token, invsrc): 84 | # Discovers an Inventory Source 85 | suffixurl = "/discoveries" 86 | uri += suffixurl 87 | invsrcuri = "/inventory-sources/" 88 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 89 | if invsrc["type"] == "EXTERNALDATADOMAIN": 90 | level = "ProtectableData" 91 | else: 92 | level = "DataCopies" 93 | payload = json.dumps({ 94 | 'start' : '/{}/{}'.format(invsrcuri, invsrc["id"]), 95 | 'level' : level 96 | }) 97 | try: 98 | response = requests.post(uri, data=payload, headers=headers, verify=False) 99 | response.raise_for_status() 100 | except requests.exceptions.RequestException as err: 101 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 102 | if response.status_code not in [200, 201, 202]: 103 | raise Exception('Failed to run discovery {} {} on level {}, code: {}, body: {}'.format( 104 | invsrc["type"], invsrc["id"], level, response.status_code, response.text)) 105 | if 'activityId' in response.json(): 106 | return response.json()['activityId'] 107 | if 'taskId' in response.json(): 108 | return response.json()['taskId'] 109 | if 'jobId' in response.json(): 110 | return response.json()['jobId'] 111 | return None 112 | 113 | def monitor_activity(ppdm, uri, token, activityid): 114 | # Monitors an activity by its ID 115 | timeout = 300 # 5 minutes timeout 116 | interval = 10 # 10 seconds interval 117 | suffixurl = "/activities/" 118 | uri += suffixurl + activityid 119 | start = time.time() 120 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 121 | while True: 122 | if (time.time() - start) > timeout: 123 | break 124 | try: 125 | response = requests.get(uri, headers=headers, verify=False) 126 | response.raise_for_status() 127 | except requests.exceptions.RequestException as err: 128 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 129 | if (response.status_code != 200): 130 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 131 | uri, response.status_code, response.text)) 132 | print('Activity {} {}'.format(activityid, response.json()['state'])) 133 | if response.json()['state'] == 'COMPLETED': 134 | return response.json()['result']['status'] 135 | time.sleep(interval) 136 | return 'TIMEOUT' 137 | 138 | def logout(ppdm, user, uri, token): 139 | # Logs out of PowerProtect 140 | suffixurl = "/logout" 141 | uri += suffixurl 142 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 143 | try: 144 | response = requests.post(uri, headers=headers, verify=False) 145 | response.raise_for_status() 146 | except requests.exceptions.RequestException as err: 147 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 148 | if (response.status_code != 204): 149 | raise Exception('Logout failed for user: {}, code: {}, body: {}'.format( 150 | user, response.status_code, response.text)) 151 | print('Logout for user: {} from PPDM: {}'.format(user, ppdm)) 152 | 153 | def main(): 154 | port = "8443" 155 | apiendpoint = "/api/v2" 156 | args = get_args() 157 | ppdm, user, password, name, invtype = args.server, args.user, args.password, args.name, args.type 158 | uri = "https://{}:{}{}".format(ppdm, port, apiendpoint) 159 | token = authenticate(ppdm, user, password, uri) 160 | invsrclist = get_inventory_src(ppdm, uri, token, name, invtype) 161 | if len(invsrclist) == 0: 162 | raise Exception('No asset was found with the provided criteria') 163 | for invsrc in invsrclist: 164 | print('Found Inventory Source {} of type {}'.format(invsrc['name'], invsrc['type'])) 165 | activityid = discover_inventory_src(ppdm, uri, token, invsrc) 166 | print('Discovery has been triggered for {}'.format(invsrc['name'])) 167 | result = monitor_activity(ppdm, uri, token, activityid) 168 | print('Discovery activity {} status {}'.format(activityid, result)) 169 | logout(ppdm, user, uri, token) 170 | 171 | if __name__ == "__main__": 172 | main() 173 | -------------------------------------------------------------------------------- /Python/removeassetsrc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import urllib3 6 | import sys 7 | import json 8 | 9 | # The purpose of this script is to simplify Inventory / Asset Source removal in PowerProtect 10 | 11 | urllib3.disable_warnings() 12 | 13 | def get_args(): 14 | # Get command line args from the user 15 | parser = argparse.ArgumentParser( 16 | description='Script to perform Asset/Inventory source removal in PowerProtect') 17 | parser.add_argument('-s', '--server', required=True, 18 | action='store', help='PPDM DNS name or IP') 19 | parser.add_argument('-usr', '--user', required=False, action='store', 20 | default='admin', help='User') 21 | parser.add_argument('-pwd', '--password', required=True, action='store', 22 | help='Password') 23 | parser.add_argument('-a', '--action', required=True, choices=['list', 'remove'], 24 | help='Choose to list all inventory sources or to remove specific one') 25 | parser.add_argument('-n', '--name', required=False, action='store', default=None, 26 | help='Name of the inventory source to list or remove') 27 | parser.add_argument('-id', '--id', required=False, action='store', default=None, 28 | help='Optionally provide the ID of the inventory source to remove') 29 | parser.add_argument('-t', '--type', required=False, action='store', 30 | default=None, help='Optionally provide the ID of the inventory source to remove') 31 | parser.add_argument('-nop', "--noprompt", required=False, default=False, action='store_true', 32 | help='No confirmation prompts would be shown if specified') 33 | args = parser.parse_args() 34 | return args 35 | 36 | def authenticate(ppdm, user, password, uri): 37 | # Login 38 | suffixurl = "/login" 39 | uri += suffixurl 40 | headers = {'Content-Type': 'application/json'} 41 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 42 | try: 43 | response = requests.post(uri, data=payload, headers=headers, verify=False) 44 | response.raise_for_status() 45 | except requests.exceptions.ConnectionError as err: 46 | print('Error Connecting to {}: {}'.format(ppdm, err)) 47 | sys.exit(1) 48 | except requests.exceptions.Timeout as err: 49 | print('Connection timed out {}: {}'.format(ppdm, err)) 50 | sys.exit(1) 51 | except requests.exceptions.RequestException as err: 52 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 53 | sys.exit(1) 54 | if (response.status_code != 200): 55 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 56 | user, response.status_code, response.text)) 57 | print('Login for user: {} to PPDM: {}'.format(user, ppdm)) 58 | token = response.json()['access_token'] 59 | return token 60 | 61 | def get_inv_src(uri, token, name, id, type): 62 | # Retrieves a list of inventory sources 63 | suffixurl = "/inventory-sources" 64 | uri += suffixurl 65 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 66 | filter = 'not type lk "DEFAULT%"' 67 | if (type is not None): 68 | if (type.lower() in ['k8s', 'k8']): 69 | type = 'KUBERNETES' 70 | elif (type.lower() == 'dd'): 71 | type = 'DATADOMAIN' 72 | filter += 'and type lk "%{}%"'.format(type) 73 | if (name is not None): 74 | filter += ' and name lk "%{}%"'.format(name) 75 | if (id is not None): 76 | filter += 'and id lk "%{}%"'.format(id) 77 | params = {'filter': filter} 78 | try: 79 | response = requests.get(uri, headers=headers, params=params, verify=False) 80 | response.raise_for_status() 81 | except requests.exceptions.RequestException as err: 82 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 83 | if (response.status_code != 200): 84 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 85 | uri, response.status_code, response.text)) 86 | return response.json()['content'] 87 | 88 | def remove_inv_src(uri, token, id): 89 | # Performs removal of inventory source by ID 90 | suffixurl = "/inventory-sources/{}".format(id) 91 | uri += suffixurl 92 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 93 | try: 94 | response = requests.delete(uri, headers=headers, verify=False) 95 | response.raise_for_status() 96 | except requests.exceptions.RequestException as err: 97 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 98 | if response.status_code not in [200, 201, 202, 204]: 99 | print('Failed to remove asset source with ID {}, code: {}, body: {}'.format( 100 | id, response.status_code, response.text)) 101 | return False 102 | return True 103 | 104 | def logout(ppdm, user, uri, token): 105 | # Logs out of PowerProtect 106 | suffixurl = "/logout" 107 | uri += suffixurl 108 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 109 | try: 110 | response = requests.post(uri, headers=headers, verify=False) 111 | response.raise_for_status() 112 | except requests.exceptions.RequestException as err: 113 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 114 | if (response.status_code != 204): 115 | raise Exception('Logout failed for user: {}, code: {}, body: {}'.format( 116 | user, response.status_code, response.text)) 117 | print('Logout for user: {} from PPDM: {}'.format(user, ppdm)) 118 | 119 | def main(): 120 | port = "8443" 121 | apiendpoint = "/api/v2" 122 | args = get_args() 123 | ppdm, user, password, action = args.server, args.user, args.password, args.action 124 | name, id, type, prompt = args.name, args.id, args.type, args.noprompt 125 | uri = "https://{}:{}{}".format(ppdm, port, apiendpoint) 126 | if (all(elem is None for elem in [name, type, id]) and action == 'remove'): 127 | print ("Please specify either inventory source name, id or type") 128 | sys.exit(5) 129 | token = authenticate(ppdm, user, password, uri) 130 | invsrc = get_inv_src(uri, token, name, id, type) 131 | if len(invsrc) == 0: 132 | print('Inventory Source could not be found') 133 | next 134 | if (action == 'list'): 135 | for asset in invsrc: 136 | print("---------------------------------------------------------") 137 | print("Inventory Source ID:", asset["id"]) 138 | print("Inventory Source Name:", asset["name"]) 139 | print("Inventory Source Type:", asset["type"]) 140 | print("Inventory Source Version:", asset["version"]) 141 | print("Status After Last Discovery:", asset["lastDiscoveryResult"]["status"]) 142 | print() 143 | elif (action == 'remove'): 144 | if (len(invsrc) > 1): 145 | print ("Inventory Source name: {} yielded in more than 1 result".format(name)) 146 | print("Narrow down the results using the --action list paramater") 147 | elif (len(invsrc) == 1): 148 | if (not prompt): 149 | print("Are you sure you would like to remove inventory source: {} ?(y/n)".format(invsrc[0]["name"])) 150 | reply = str(input().lower().rstrip()) 151 | if (reply[:1] not in ['y', 'yes']): 152 | print("Inventory Source: {} will not be removed".format(invsrc[0]["name"])) 153 | logout(ppdm, user, uri, token) 154 | sys.exit(10) 155 | if invsrc[0]["type"] == "KUBERNETES": 156 | print("Removing K8s Inventory Source is not currently supported") 157 | logout(ppdm, user, uri, token) 158 | sys.exit(5) 159 | print("Removing Inventory Source with name: {} and of type {}".format(invsrc[0]["name"], invsrc[0]["type"])) 160 | result = remove_inv_src(uri, token, invsrc[0]["id"]) 161 | if result: 162 | print("Inventory Source: {} removed successfully".format(invsrc[0]["name"])) 163 | else: 164 | print("Inventory Source: {} could not be removed".format(invsrc[0]["name"])) 165 | logout(ppdm, user, uri, token) 166 | 167 | if __name__ == "__main__": 168 | main() 169 | -------------------------------------------------------------------------------- /Python/credsmgmt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import urllib3 6 | import sys 7 | import json 8 | 9 | # The purpose of this script is to simplify K8s Credential Management in PowerProtect 10 | 11 | urllib3.disable_warnings() 12 | 13 | def get_args(): 14 | # Get command line args from the user 15 | parser = argparse.ArgumentParser( 16 | description='Script to simplify K8s credentials management in PowerProtect') 17 | parser.add_argument('-s', '--server', required=True, 18 | action='store', help='PPDM DNS name or IP') 19 | parser.add_argument('-usr', '--user', required=False, action='store', 20 | default='admin', help='User') 21 | parser.add_argument('-pwd', '--password', required=True, action='store', 22 | help='Password') 23 | parser.add_argument('-a', '--action', required=True, choices=['add', 'remove'], 24 | help='Choose to add credentials or remove one of them') 25 | parser.add_argument('-n', '--name', required=False, action='store', default=None, 26 | help='Name of the credentials to add or remove') 27 | parser.add_argument('-id', '--id', required='add' in sys.argv, action='store', default=None, 28 | help='Optionally provide the ID the credentials to add or remove') 29 | parser.add_argument('-token', '--token', required='add' in sys.argv, action='store', default=None, 30 | help='Provide the token of the credentials to add') 31 | parser.add_argument('-nop', "--noprompt", required=False, default=False, action='store_true', 32 | help='No confirmation prompts would be shown if specified') 33 | args = parser.parse_args() 34 | return args 35 | 36 | def authenticate(ppdm, user, password, uri): 37 | # Login 38 | suffixurl = "/login" 39 | uri += suffixurl 40 | headers = {'Content-Type': 'application/json'} 41 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 42 | try: 43 | response = requests.post(uri, data=payload, headers=headers, verify=False) 44 | response.raise_for_status() 45 | except requests.exceptions.ConnectionError as err: 46 | print('Error Connecting to {}: {}'.format(ppdm, err)) 47 | sys.exit(1) 48 | except requests.exceptions.Timeout as err: 49 | print('Connection timed out {}: {}'.format(ppdm, err)) 50 | sys.exit(1) 51 | except requests.exceptions.RequestException as err: 52 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 53 | sys.exit(1) 54 | if (response.status_code != 200): 55 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 56 | user, response.status_code, response.text)) 57 | print('Login for user: {} to PPDM: {}'.format(user, ppdm)) 58 | authtoken = response.json()['access_token'] 59 | return authtoken 60 | 61 | def add_creds(uri, authtoken, name, token): 62 | # Adds credentials 63 | suffixurl = "/credentials" 64 | uri += suffixurl 65 | type = "KUBERNETES" 66 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(authtoken)} 67 | payload = json.dumps({ 68 | "name" : name, 69 | "username" : "null", 70 | "password" : token, 71 | "type" : type, 72 | "method" : "TOKEN", 73 | "internal" : "false" 74 | }) 75 | try: 76 | response = requests.post(uri, data=payload, headers=headers, verify=False) 77 | response.raise_for_status() 78 | except requests.exceptions.RequestException as err: 79 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 80 | if response.status_code not in [200, 201, 202, 204]: 81 | print('Failed to remove credentials with ID {}, code: {}, body: {}'.format( 82 | id, response.status_code, response.text)) 83 | return False 84 | return True 85 | 86 | def get_creds(uri, authtoken, name, id): 87 | # Retrieves a list of credentials 88 | suffixurl = "/credentials" 89 | uri += suffixurl 90 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(authtoken)} 91 | filter = 'internal eq false' 92 | if id != None: 93 | filter += ' and id lk "%{}%"'.format(id) 94 | if name != None: 95 | filter += ' and name lk "%{}%"'.format(name) 96 | params = {'filter': filter} 97 | try: 98 | response = requests.get(uri, headers=headers, params=params, verify=False) 99 | response.raise_for_status() 100 | except requests.exceptions.RequestException as err: 101 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 102 | if (response.status_code != 200): 103 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 104 | uri, response.status_code, response.text)) 105 | return response.json()['content'] 106 | 107 | def remove_creds(uri, authtoken, id): 108 | # Performs removal of credentials by ID 109 | suffixurl = "/credentials/{}".format(id) 110 | uri += suffixurl 111 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(authtoken)} 112 | try: 113 | response = requests.delete(uri, headers=headers, verify=False) 114 | response.raise_for_status() 115 | except requests.exceptions.RequestException as err: 116 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 117 | if response.status_code not in [200, 201, 202, 204]: 118 | print('Failed to remove credentials with ID {}, code: {}, body: {}'.format( 119 | id, response.status_code, response.text)) 120 | return False 121 | return True 122 | 123 | def logout(ppdm, user, uri, authtoken): 124 | # Logs out of PowerProtect 125 | suffixurl = "/logout" 126 | uri += suffixurl 127 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(authtoken)} 128 | try: 129 | response = requests.post(uri, headers=headers, verify=False) 130 | response.raise_for_status() 131 | except requests.exceptions.RequestException as err: 132 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 133 | if (response.status_code != 204): 134 | raise Exception('Logout failed for user: {}, code: {}, body: {}'.format( 135 | user, response.status_code, response.text)) 136 | print('Logout for user: {} from PPDM: {}'.format(user, ppdm)) 137 | 138 | def main(): 139 | port = "8443" 140 | apiendpoint = "/api/v2" 141 | args = get_args() 142 | ppdm, user, password, action = args.server, args.user, args.password, args.action 143 | name, token, prompt, id = args.name, args.token, args.noprompt, args.id 144 | uri = "https://{}:{}{}".format(ppdm, port, apiendpoint) 145 | authtoken = authenticate(ppdm, user, password, uri) 146 | if (action == 'add'): 147 | result = add_creds(uri, authtoken, name, token) 148 | if result: 149 | print("Credentials: {} added successfully".format(name)) 150 | elif (action == 'remove'): 151 | credlist = get_creds(uri, authtoken, name, id) 152 | if (len(credlist) == 0): 153 | print ("Could not match credentials name: {}".format(name)) 154 | elif (len(credlist) > 1): 155 | print ("Credentials name: {} yielded in more than 1 result".format(name)) 156 | print("Narrow down the results using the exact name") 157 | for index in range(len(credlist)): 158 | print ("Credentials Name: {} with ID: {}".format(credlist[index]["name"], credlist[index]["id"])) 159 | elif (len(credlist) == 1): 160 | if (not prompt): 161 | print("Are you sure you would like to remove credentials: {} ?(y/n)".format(credlist[0]["name"])) 162 | reply = str(input().lower().rstrip()) 163 | if (reply[:1] not in ['y', 'yes']): 164 | print("Credentials: {} will not be removed".format(credlist[0]["name"])) 165 | logout(ppdm, user, uri, authtoken) 166 | sys.exit(10) 167 | print("Removing credentials with Name: {} and of ID: {}".format(credlist[0]["name"], credlist[0]["id"])) 168 | result = remove_creds(uri, authtoken, credlist[0]["id"]) 169 | if result: 170 | print("Credentials: {} removed successfully".format(credlist[0]["name"])) 171 | else: 172 | print("Credentials: {} could not be removed".format(credlist[0]["name"])) 173 | logout(ppdm, user, uri, authtoken) 174 | 175 | if __name__ == "__main__": 176 | main() 177 | -------------------------------------------------------------------------------- /Python/updateprotectionpolicyschedule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import urllib3 6 | import json 7 | import sys 8 | 9 | # Update the schedule of an existing Protection Policy in PowerProtect 10 | 11 | urllib3.disable_warnings() 12 | 13 | def get_args(): 14 | # Get command line args from the user 15 | parser = argparse.ArgumentParser( 16 | description='Script to update Protection Policy Schedule in PowerProtect Data Manager') 17 | parser.add_argument('-s', '--server', required=True, 18 | action='store', help='PPDM DNS name or IP') 19 | parser.add_argument('-usr', '--user', required=False, action='store', 20 | default='admin', help='User') 21 | parser.add_argument('-pwd', '--password', required=True, action='store', 22 | help='Password') 23 | 24 | parser.add_argument('-name', '--assetname', required=True, action='store', 25 | help='Provide the name of the protected asset') 26 | parser.add_argument('-type', '--assettype', required=True, action='store', 27 | help='Provide the type of protected asset') 28 | 29 | parser.add_argument('-action', '--action', required=True, choices=['display_schedule', 'update_schedule'], 30 | help='Display the existing schedule or update the existing schedule') 31 | 32 | parser.add_argument('-json', '--jsonfile', required=('update_schedule' in sys.argv), 33 | help='Provide the json file name with the json body') 34 | 35 | args = parser.parse_args() 36 | return args 37 | 38 | def authenticate(ppdm, user, password, uri): 39 | # Login 40 | print('\nRetrieving authorization token') 41 | loginendpoint = '/login' 42 | uri += loginendpoint 43 | headers = {'Content-Type': 'application/json'} 44 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 45 | try: 46 | response = requests.post( 47 | uri, data=payload, headers=headers, verify=False) 48 | response.raise_for_status() 49 | if (response.status_code != 200): 50 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 51 | user, response.status_code, response.text)) 52 | print('\nUser: {} logged in to PPDM: {}'.format(user, ppdm)) 53 | except requests.exceptions.ConnectionError as errc: 54 | print('\nError Connecting to {}: {}'.format(ppdm, errc)) 55 | sys.exit() 56 | except requests.exceptions.Timeout as errt: 57 | print('\nConnection timed out, make sure that there is connectivity to {}: {}'.format( 58 | ppdm, errt)) 59 | sys.exit() 60 | except requests.exceptions.RequestException: 61 | print('\nThe call {} {} failed with exception:{}'.format( 62 | response.request.method, response.url, response.text)) 63 | sys.exit() 64 | # Parse the response and extract the access_token 65 | accesstoken = response.json()['access_token'] 66 | return accesstoken 67 | 68 | def get_asset_details(uri, accesstoken, assetname, assettype): 69 | # Get asset using the type and name and parse the protection policy id 70 | print('\nFetching asset using name: {} and type: {}'.format( 71 | assetname, assettype)) 72 | assetsendpoint = '/assets' 73 | assetsapifilter = '?filter=type%20eq%20%22'+assettype + \ 74 | '%22%20and%20name%20lk%20%22%25'+assetname+'%25%22' 75 | uri += assetsendpoint + assetsapifilter 76 | headers = {'Content-Type': 'application/json', 77 | 'Authorization': 'Bearer {}'.format(accesstoken)} 78 | try: 79 | response = requests.get(uri, headers=headers, verify=False) 80 | response.raise_for_status() 81 | if (response.status_code != 200): 82 | raise Exception('\nCould not fetch asset details: , code: {}, body: {}'.format( 83 | response.status_code, response.text)) 84 | if len(response.json()['content']) == 0: 85 | print('\nNo asset found matching criteria - name: {} and type: {}'.format( 86 | assetname, assettype)) 87 | sys.exit() 88 | elif len(response.json()['content']) > 1: 89 | print( 90 | '\nMultiple assets found matching criteria. Please refine the search further.') 91 | sys.exit() 92 | else: 93 | assetid = response.json()['content'][0]['id'] 94 | assetname = response.json()['content'][0]['name'] 95 | protectionpolicyid = response.json( 96 | )['content'][0]['protectionPolicyId'] 97 | if protectionpolicyid is None: 98 | print('\nAsset {} not protected under any policy'.format(assetname)) 99 | sys.exit() 100 | else: 101 | print('\nAsset id : {}, Asset Name : {}, Protection Policy id : {}'.format( 102 | assetid, assetname, protectionpolicyid)) 103 | return protectionpolicyid 104 | except requests.exceptions.RequestException: 105 | print('\nThe call {}{} failed with exception:{}'.format( 106 | response.request.method, response.url, response.text)) 107 | sys.exit() 108 | 109 | def get_policy_details(uri, accesstoken, protectionpolicyid): 110 | # Fetch the details of the protection policy retrieved in 3 111 | print('\nFetching protection policy') 112 | protectionpolicyendpoint = '/protection-policies/' + protectionpolicyid 113 | uri += protectionpolicyendpoint 114 | headers = {'Content-Type': 'application/json', 115 | 'Authorization': 'Bearer {}'.format(accesstoken)} 116 | try: 117 | # Call the Assets GET API with filter on type and name 118 | response = requests.get(uri, headers=headers, verify=False) 119 | response.raise_for_status() 120 | if (response.status_code != 200): 121 | raise Exception('\nFailed to get details of policy id: {}, code: {}, body: {}'.format( 122 | protectionpolicyid, response.status_code, response.text)) 123 | print('\nProtection Policy: {}' .format(protectionpolicyid)) 124 | print(response.text) 125 | except requests.exceptions.RequestException: 126 | print('\nThe call {} {} failed with exception:{}'.format( 127 | response.request.method, response.url, response.text)) 128 | sys.exit() 129 | 130 | def update_policy_schedule(uri, accesstoken, protectionpolicyid, jsonfile): 131 | # Update the schedule of the protection policy 132 | print('\nUpdating protection policy schedule: {}'.format(protectionpolicyid)) 133 | updatepolicyendpoint = '/protection-policies/' + protectionpolicyid 134 | uri += updatepolicyendpoint 135 | # Read the json from a file 136 | payload = open(jsonfile, 'rb').read() 137 | headers = {'Content-Type': 'application/json', 138 | 'Authorization': 'Bearer {}'.format(accesstoken)} 139 | try: 140 | # Call the Login API with username and password 141 | response = requests.put(uri, headers=headers, 142 | data=payload, verify=False) 143 | response.raise_for_status() 144 | if response.status_code not in [200, 201, 202]: 145 | raise Exception('\nFailed to update policy {}, code: {}, body: {}'.format( 146 | protectionpolicyid, response.status_code, response.text)) 147 | print('\nProtection policy updated successfully') 148 | except requests.exceptions.RequestException: 149 | print('\nThe call {} {} failed with exception:{}'.format( 150 | response.request.method, response.url, response.text)) 151 | sys.exit() 152 | 153 | def logout(ppdm, user, uri, accesstoken): 154 | # Logout 155 | print('\nLogging out of the current session') 156 | logoutendpoint = '/logout' 157 | uri += logoutendpoint 158 | headers = {'Content-Type': 'application/json', 159 | 'Authorization': 'Bearer {}'.format(accesstoken)} 160 | try: 161 | response = requests.post( 162 | uri, headers=headers, verify=False) 163 | response.raise_for_status() 164 | if (response.status_code != 204): 165 | raise Exception('\nLogout failed for user: {}, code: {}, body: {}'.format( 166 | user, response.status_code, response.text)) 167 | print('\nUser: {} logged out of PPDM: {}'.format(user, ppdm)) 168 | except requests.exceptions.RequestException: 169 | print('\nThe call {} {} failed with exception:{}'.format( 170 | response.request.method, response.url, response.text)) 171 | sys.exit() 172 | 173 | def main(): 174 | port = '8443' 175 | apiendpoint = '/api/v2' 176 | args = get_args() 177 | ppdm = args.server 178 | user = args.user 179 | password = args.password 180 | assetname = args.assetname 181 | assettype = args.assettype 182 | action = args.action 183 | jsonfile = args.jsonfile 184 | uri = 'https://{}:{}{}'.format(ppdm, port, apiendpoint) 185 | accesstoken = authenticate(ppdm, user, password, uri) 186 | protectionpolicyid = get_asset_details( 187 | uri, accesstoken, assetname, assettype) 188 | if 'display_schedule' == action: 189 | get_policy_details(uri, accesstoken, protectionpolicyid) 190 | elif 'update_schedule' == action: 191 | update_policy_schedule(uri, accesstoken, 192 | protectionpolicyid, jsonfile) 193 | logout(ppdm, user, uri, accesstoken) 194 | 195 | if __name__ == "__main__": 196 | main() 197 | -------------------------------------------------------------------------------- /Python/restorevmorig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import json 6 | import sys 7 | import urllib3 8 | from sys import argv 9 | import time 10 | 11 | # The purpose of this script to facilitiate restore of a VM to its original location 12 | 13 | urllib3.disable_warnings() 14 | 15 | def get_args(): 16 | # Get command line args from the user 17 | parser = argparse.ArgumentParser( 18 | description='Script to manage Assets in PowerProtect Data Manager') 19 | parser.add_argument('-s', '--server', required=True, 20 | action='store', help='PPDM DNS name or IP') 21 | parser.add_argument('-usr', '--user', required=False, action='store', 22 | default='admin', help='User') 23 | parser.add_argument('-pwd', '--password', required=True, action='store', 24 | help='Password') 25 | parser.add_argument('-n', '--vmname', required=True, action='store', 26 | help='The name of the VM to recover') 27 | parser.add_argument('-a', '--action', choices=['get_backups', 'recover'], 28 | default='get_backups', required=True, 29 | help='Get the list of backups or recover using a backup ID') 30 | parser.add_argument('-bckid','--backupid', required=('recover' in argv), 31 | help='The ID of the backup to recover from') 32 | args = parser.parse_args() 33 | return args 34 | 35 | def authenticate(ppdm, user, password, uri): 36 | # Login 37 | suffixurl = "/login" 38 | uri += suffixurl 39 | headers = {'Content-Type': 'application/json'} 40 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 41 | try: 42 | response = requests.post(uri, data=payload, headers=headers, verify=False) 43 | response.raise_for_status() 44 | except requests.exceptions.ConnectionError as err: 45 | print('Error Connecting to {}: {}'.format(ppdm, err)) 46 | sys.exit(1) 47 | except requests.exceptions.Timeout as err: 48 | print('Connection timed out {}: {}'.format(ppdm, err)) 49 | sys.exit(1) 50 | except requests.exceptions.RequestException as err: 51 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 52 | sys.exit(1) 53 | if (response.status_code != 200): 54 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 55 | user, response.status_code, response.text)) 56 | print('Login for user: {} to PPDM: {}'.format(user, ppdm)) 57 | token = response.json()['access_token'] 58 | return token 59 | 60 | def get_protected_vm(ppdm, uri, token, vmname): 61 | # Returns the VM based on its name 62 | suffixurl = "/assets" 63 | uri += suffixurl 64 | vmfilter = "VMWARE_VIRTUAL_MACHINE" 65 | protectedfilter="protectionPolicyId ne null" 66 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 67 | filter = 'type eq "{}" and name lk "{}" and {}'.format(vmfilter, vmname, protectedfilter) 68 | params = {'filter': filter} 69 | try: 70 | response = requests.get(uri, headers=headers, params=params, verify=False) 71 | response.raise_for_status() 72 | except requests.exceptions.RequestException as err: 73 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 74 | if (response.status_code != 200): 75 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 76 | uri, response.status_code, response.text)) 77 | return response.json()['content'] 78 | 79 | def get_backups(ppdm, uri, token, vmid, name): 80 | # Returns a list of backups for a given 81 | suffixurl = "/copies" 82 | prefixurl = "/assets" 83 | uri += "{}/{}{}".format(prefixurl, vmid, suffixurl) 84 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 85 | try: 86 | response = requests.get(uri, headers=headers, verify=False) 87 | response.raise_for_status() 88 | except requests.exceptions.RequestException as err: 89 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 90 | if (response.status_code != 200): 91 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 92 | uri, response.status_code, response.text)) 93 | assetlist = response.json()["content"] 94 | for elem in assetlist: 95 | print("---------------------------------------------------------") 96 | print(" Backup ID:", elem["id"]) 97 | print(" Creation Time:", elem["createTime"]) 98 | print(" Backup Size:", elem["size"]) 99 | print(" Copy Type:", elem["copyType"]) 100 | print(" Retention Time:", elem["retentionTime"]) 101 | print(" Adhoc Backup:", elem["adhocBackup"]) 102 | print(" Backup Array Type:", elem["details"]["arraySubType"]) 103 | print(" Array Serial No:", elem["details"]["arraySerialNo"]) 104 | print() 105 | 106 | def recover_vm_to_orig(ppdm, uri, token, copyid, name): 107 | # Recovers a VM to its original location 108 | suffixurl = "/restored-copies" 109 | uri += suffixurl 110 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 111 | desc = 'Restore {} to original production'.format(name) 112 | payload = json.dumps({ 113 | 'description' : desc, 114 | 'copyId' : copyid, 115 | 'restoreType': 'TO_PRODUCTION' 116 | }) 117 | try: 118 | response = requests.post(uri, data=payload, headers=headers, verify=False) 119 | response.raise_for_status() 120 | except requests.exceptions.RequestException as err: 121 | print("The call {}{} failed with exception:{}".format(response.request.method, response.url, err)) 122 | if (response.status_code != 201): 123 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 124 | uri, response.status_code, response.text)) 125 | if 'activityId' in response.json(): 126 | return response.json()['activityId'] 127 | if 'taskId' in response.json(): 128 | return response.json()['taskId'] 129 | if 'jobId' in response.json(): 130 | return response.json()['jobId'] 131 | return None 132 | 133 | def monitor_activity(ppdm, uri, token, activityid): 134 | # Monitors an activity by its ID 135 | timeout = 1800 # 30 minutes timeout 136 | interval = 10 # 10 seconds interval 137 | suffixurl = "/activities/" 138 | uri += suffixurl + activityid 139 | start = time.time() 140 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 141 | while True: 142 | if (time.time() - start) > timeout: 143 | break 144 | try: 145 | response = requests.get(uri, headers=headers, verify=False) 146 | response.raise_for_status() 147 | except requests.exceptions.RequestException as err: 148 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 149 | if (response.status_code != 200): 150 | raise Exception('Failed to query {}, code: {}, body: {}'.format( 151 | uri, response.status_code, response.text)) 152 | print('Activity {} {}'.format(activityid, response.json()['state'])) 153 | if response.json()['state'] == 'COMPLETED': 154 | return response.json()['result']['status'] 155 | time.sleep(interval) 156 | return 'TIMEOUT' 157 | 158 | def logout(ppdm, user, uri, token): 159 | # Logs out of PowerProtect 160 | suffixurl = "/logout" 161 | uri += suffixurl 162 | headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token)} 163 | try: 164 | response = requests.post(uri, headers=headers, verify=False) 165 | response.raise_for_status() 166 | except requests.exceptions.RequestException as err: 167 | print("The call {} {} failed with exception:{}".format(response.request.method, response.url, err)) 168 | if (response.status_code != 204): 169 | raise Exception('Logout failed for user: {}, code: {}, body: {}'.format( 170 | user, response.status_code, response.text)) 171 | print('Logout for user: {} from PPDM: {}'.format(user, ppdm)) 172 | 173 | def main(): 174 | port = "8443" 175 | apiendpoint = "/api/v2" 176 | args = get_args() 177 | ppdm, user, password, vmname, action = args.server, args.user, args.password, args.vmname, args.action 178 | uri = "https://{}:{}{}".format(ppdm, port, apiendpoint) 179 | token = authenticate(ppdm, user, password, uri) 180 | vms = get_protected_vm(ppdm, uri, token, vmname) 181 | if len(vms) == 0: 182 | print('No Virtual Machine name matches the name criteria: {}'.format(vmname)) 183 | elif len(vms) > 1: 184 | print('Found more than one Virtual Machine which matches: {}'.format(vmname)) 185 | for vm in vms: 186 | print('{} : {}'.format(vm['name'], vm['id'])) 187 | print('Please specify the exact Virtual Machine name') 188 | else: 189 | vmid = vms[0]['id'] 190 | name = vms[0]['name'] 191 | if (action == "get_backups"): 192 | get_backups(ppdm, uri, token, vmid, name) 193 | elif action == "recover": 194 | copyid = args.backupid 195 | activityid = recover_vm_to_orig(ppdm, uri, token, copyid, name) 196 | result = monitor_activity(ppdm, uri, token, activityid) 197 | print('Restore activity {} status {}'.format(activityid, result)) 198 | logout(ppdm, user, uri, token) 199 | 200 | if __name__ == "__main__": 201 | main() 202 | -------------------------------------------------------------------------------- /Python/policy2dd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import argparse 5 | import requests 6 | import urllib3 7 | 8 | # The purpose of this script is to report on Policy to Data Domain info 9 | # Examples: 10 | # python policy2dd.py -s 10.0.0.1 -usr admin -pwd "myPassword!" 11 | # python policy2dd.py -s 10.0.0.1 pwd "myPassword!" -n prod_policy 12 | 13 | 14 | urllib3.disable_warnings() 15 | 16 | 17 | def get_args(): 18 | """Get command line args from the user""" 19 | parser = argparse.ArgumentParser( 20 | description='Script to show Policy to Data Domain information in PPDM') 21 | parser.add_argument('-s', '--server', required=True, 22 | action='store', help='PPDM DNS name or IP') 23 | parser.add_argument('-usr', '--user', required=False, action='store', 24 | default='admin', help='User') 25 | parser.add_argument('-pwd', '--password', required=True, action='store', 26 | help='Password') 27 | parser.add_argument('-n', '--name', required=False, default=None, 28 | help='Optionally specify policy to query') 29 | args = parser.parse_args() 30 | return args 31 | 32 | 33 | def init_rest_call(verb, uri, token, payload=None, params=None): 34 | """Generic function for REST calls""" 35 | if uri.endswith("/login"): 36 | headers = {"Content-Type": "application/json"} 37 | else: 38 | headers = { 39 | "Content-Type": "application/json", 40 | "Authorization": "Bearer " f"{token}", 41 | } 42 | payload = json.dumps(payload) 43 | verify = False 44 | timeout = 90 45 | try: 46 | if verb.lower() == "get": 47 | response = requests.get( 48 | uri, 49 | headers=headers, 50 | params=params, 51 | verify=verify, 52 | timeout=timeout 53 | ) 54 | else: 55 | response = requests.request( 56 | verb, 57 | uri, 58 | headers=headers, 59 | params=params, 60 | data=payload, 61 | verify=verify, 62 | timeout=timeout, 63 | ) 64 | response.raise_for_status() 65 | except requests.exceptions.ConnectionError as error: 66 | print(f"-> Error Connecting to {uri}: {error}") 67 | raise SystemExit(1) from error 68 | except requests.exceptions.Timeout as error: 69 | print(f"-> Connection timed out {urllib3}: {error}") 70 | raise SystemExit(1) from error 71 | except requests.exceptions.RequestException as error: 72 | if response.status_code in (401, 502): 73 | return False 74 | print( 75 | f"-> The call {response.request.method} {response.url} \ 76 | failed with exception:{error}" 77 | ) 78 | if not response.content: 79 | return True 80 | if uri.endswith("/login"): 81 | return response.json()["access_token"] 82 | try: 83 | return response.json() 84 | except (AttributeError, ValueError): 85 | return response.content 86 | 87 | 88 | def authenticate(uri, user, password): 89 | """Logins into PowerProtect Data Manager""" 90 | uri = f"{uri}/login" 91 | payload = {"username": user, "password": password} 92 | token = init_rest_call("POST", uri, payload, payload) 93 | return token 94 | 95 | 96 | def get_version(uri, token): 97 | """Gets the PPDM version""" 98 | uri = f"{uri}/nodes" 99 | response = init_rest_call("GET", uri, token) 100 | if "version" in response["content"][0]: 101 | return response["content"][0]["version"] 102 | print("Could not determine PPDM version. Exiting...") 103 | raise SystemExit(1) 104 | 105 | 106 | def get_policy(uri, token, policy_name): 107 | """Get configured protection policies""" 108 | query_params = None 109 | if "/api/v3" in uri: 110 | uri = f"{uri}/policies" 111 | else: 112 | uri = f"{uri}/protection-policies" 113 | if policy_name is not None: 114 | query = f'name eq "{policy_name}"' 115 | query_params = {'filter': query} 116 | response = init_rest_call("GET", uri, token, None, query_params) 117 | if "content" not in response or len(response["content"]) == 0: 118 | return False 119 | return response["content"] 120 | 121 | 122 | def get_storage_info(policy): 123 | """Get Data Domain storage info""" 124 | policy.setdefault("dpType", []) 125 | policy.setdefault("ddId", []) 126 | policy.setdefault("suId", []) 127 | policy.setdefault("ddNic", []) 128 | for stage in policy["stages"]: 129 | if stage["type"] == "PROTECTION": 130 | stage["type"] = "BACKUP" 131 | policy["dpType"].append(stage["type"]) 132 | policy["ddId"].append(stage["target"]["storageContainerId"]) 133 | policy["suId"].append(stage["target"]["storageTargetId"]) 134 | policy["ddNic"].append(stage["target"]["preferredInterfaceId"]) 135 | return policy 136 | 137 | 138 | def get_storage_info_v3(policy): 139 | """Get Data Domain storage info for API v3""" 140 | policy.setdefault("dpType", []) 141 | policy.setdefault("ddId", []) 142 | policy.setdefault("suId", []) 143 | policy.setdefault("ddNic", []) 144 | for objective in policy["objectives"]: 145 | policy["dpType"].append(objective["type"]) 146 | policy["ddId"].append(objective["target"]["storageContainerId"]) 147 | policy["suId"].append(objective["target"]["storageTargetId"]) 148 | policy["ddNic"].append(objective["target"]["preferredInterfaceId"]) 149 | return policy 150 | 151 | 152 | def get_dd_name(uri, token, policy): 153 | """Get Data Domain storage name by ID""" 154 | uri = f"{uri}/storage-systems" 155 | policy.setdefault("ddName", []) 156 | for counter in range(len(policy["dpType"])): 157 | query = 'type eq "DATA_DOMAIN_SYSTEM"' 158 | query += f' and id eq "{policy["ddId"][counter]}"' 159 | query_params = {'filter': query} 160 | response = init_rest_call("GET", uri, token, False, query_params) 161 | if "content" in response: 162 | if len(response["content"]) == 1: 163 | policy["ddName"].append(response["content"][0]["name"]) 164 | else: 165 | policy["ddName"][counter] = False 166 | return policy 167 | 168 | 169 | def get_dd_storageunit(uri, token, policy): 170 | "Gets Storage Unit name by ID" 171 | uri = f"{uri}/datadomain-mtrees" 172 | policy.setdefault("suName", []) 173 | for counter in range(len(policy["dpType"])): 174 | query = f'storageSystem.id eq "{policy["ddId"][counter]}"' 175 | query += f' and id eq "{policy["suId"][counter]}"' 176 | query += ' and type eq "DDSTORAGEUNIT"' 177 | query_params = {'filter': query} 178 | response = init_rest_call("GET", uri, token, None, query_params) 179 | if "content" not in response: 180 | return False 181 | policy["suName"].append(response["content"][0]["name"]) 182 | return policy 183 | 184 | 185 | def main(): 186 | api_port = "8443" 187 | api_endpoint = "/api/v2" 188 | api_v3_release = 19.16 189 | api_v3_endpoint = "/api/v3" 190 | api_v3 = False 191 | 192 | args = get_args() 193 | ppdm, user, password = args.server, args.user, args.password 194 | policy_name = args.name 195 | 196 | uri = f"https://{ppdm}:{api_port}{api_endpoint}" 197 | token = authenticate(uri, user, password) 198 | version = get_version(uri, token) 199 | 200 | if float(version[:5]) > api_v3_release: 201 | print("Using The PowerProtect Data Manager v3 API") 202 | api_v3 = True 203 | uri_v3 = f"https://{ppdm}:{api_port}{api_v3_endpoint}" 204 | policies = get_policy(uri_v3, token, policy_name) 205 | else: 206 | policies = get_policy(uri, token, policy_name) 207 | if not policies: 208 | if policy_name is not None: 209 | print("Policy could not be found. Exiting...") 210 | raise SystemExit(5) 211 | print("No policies found. Exiting...") 212 | raise SystemExit(5) 213 | for policy in policies: 214 | print("------------------------------------------------------") 215 | print("Policy Name:", policy["name"]) 216 | print("Policy ID:", policy["id"]) 217 | print("Policy Type:", policy["assetType"]) 218 | if api_v3: 219 | print("Policy Disabled:", policy["disabled"]) 220 | policy = get_storage_info_v3(policy) 221 | else: 222 | print("Policy Enabled:", policy["enabled"]) 223 | policy = get_storage_info(policy) 224 | policy = get_dd_name(uri, token, policy) 225 | if False in policy["ddName"]: 226 | print("Could not retrieve Data Domain Info") 227 | policy = get_dd_storageunit(uri, token, policy) 228 | if not policy["suName"]: 229 | print("Could not retrieve Storage Unit Info") 230 | if len(policy["dpType"]) == 1: 231 | policy["dpType"] = policy["dpType"][0] 232 | policy["ddName"] = policy["ddName"][0] 233 | policy["suName"] = policy["suId"][0] 234 | policy["ddNic"] = policy["ddNic"][0] 235 | print("Data Protection Operation:", policy["dpType"]) 236 | print("Data Domain Name:", policy["ddName"]) 237 | print("Data Domain SU Name:", policy["suName"]) 238 | print("Data Domain NIC:", policy["ddNic"]) 239 | print() 240 | 241 | 242 | if __name__ == "__main__": 243 | main() 244 | -------------------------------------------------------------------------------- /PowerShell7/Standalone/triageFailedActivity.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/PowerShell/releases/tag/v7.3.4 4 | #> 5 | 6 | # VARS 7 | $ppdm = 'ppdm-01.vcorp.local' 8 | $user = 'VCORP\powerprotect' 9 | 10 | <# 11 | DO NOT MODIFY BELOW THIS LINE 12 | #> 13 | 14 | # GLOBAL VARS 15 | $global:ApiVersion = 'v2' 16 | $global:Port = 8443 17 | $global:AuthObject = $null 18 | 19 | # PAGE SIZE RETURNED FROM POWERPROTECT DATA MANAGER 20 | $pagesize = 100 21 | 22 | function connect-dmapi { 23 | <# 24 | .SYNOPSIS 25 | Connect to the PowerProtect Data Manager REST API. 26 | 27 | .DESCRIPTION 28 | Creates a credentials file for PowerProtect Data Manager if one does not exist. 29 | Connects to the PowerProtect Data Manager REST API 30 | 31 | .PARAMETER Server 32 | Specifies the FQDN of the PowerProtect Data Manager server. 33 | 34 | .OUTPUTS 35 | System.Object 36 | $global:AuthObject 37 | 38 | .EXAMPLE 39 | PS> connect-dmapi -Server 'ppdm-01.vcorp.local' 40 | 41 | .LINK 42 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 43 | 44 | #> 45 | [CmdletBinding()] 46 | param ( 47 | [Parameter( Mandatory=$true)] 48 | [string]$Server 49 | ) 50 | begin { 51 | # CHECK TO SEE IF CREDS FILE EXISTS IF NOT CREATE ONE 52 | $Exists = Test-Path -Path ".\$($Server).xml" -PathType Leaf 53 | if($Exists) { 54 | $Credential = Import-CliXml ".\$($Server).xml" 55 | } else { 56 | $Credential = Get-Credential -Message "Credentials for $($Server)" 57 | $Credential | Export-CliXml ".\$($Server).xml" 58 | } 59 | } 60 | process { 61 | $Login = @{ 62 | username="$($Credential.username)" 63 | password="$(ConvertFrom-SecureString -SecureString $Credential.password -AsPlainText)" 64 | } 65 | # LOGON TO THE POWERPROTECT API 66 | $Auth = Invoke-RestMethod -Uri "https://$($Server):$($Port)/api/$($ApiVersion)/login" ` 67 | -Method POST ` 68 | -ContentType 'application/json' ` 69 | -Body (ConvertTo-Json $Login) ` 70 | -SkipCertificateCheck 71 | $Object = @{ 72 | server ="https://$($Server):$($Port)/api/$($ApiVersion)" 73 | token= @{ 74 | authorization="Bearer $($Auth.access_token)" 75 | } #END TOKEN 76 | } #END AUTHOBJ 77 | 78 | $global:AuthObject = $Object 79 | 80 | $global:AuthObject | Format-List 81 | 82 | } # END PROCESS 83 | } # END FUNCTION 84 | 85 | function disconnect-dmapi { 86 | <# 87 | .SYNOPSIS 88 | Disconnect from the PowerProtect Data Manager REST API. 89 | 90 | .DESCRIPTION 91 | Destroys the bearer token contained with $global:AuthObject 92 | 93 | .OUTPUTS 94 | System.Object 95 | $global:AuthObject 96 | 97 | .EXAMPLE 98 | PS> disconnect-dmapi 99 | 100 | .LINK 101 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 102 | 103 | #> 104 | [CmdletBinding()] 105 | param ( 106 | ) 107 | begin {} 108 | process { 109 | #LOGOFF OF THE POWERPROTECT API 110 | Invoke-RestMethod -Uri "$($AuthObject.server)/logout" ` 111 | -Method POST ` 112 | -ContentType 'application/json' ` 113 | -Headers ($AuthObject.token) ` 114 | -SkipCertificateCheck 115 | 116 | $global:AuthObject = $null 117 | } 118 | } # END FUNCTION 119 | 120 | function get-dmactivities { 121 | <# 122 | .SYNOPSIS 123 | Get PowerProtect Data Manager activities 124 | 125 | .DESCRIPTION 126 | Get PowerProtect Data Manager activities based on filters 127 | 128 | .PARAMETER Filters 129 | An array of values used to filter the query 130 | 131 | .PARAMETER PageSize 132 | An int representing the desired number of elements per page 133 | 134 | .OUTPUTS 135 | System.Array 136 | 137 | .EXAMPLE 138 | PS> # GET ACTIVITIES BASED ON A FILTER 139 | PS> $Date = (Get-Date).AddDays(-1) 140 | PS> $Filters = @( 141 | "classType eq `"JOB`"" 142 | "and category eq `"PROTECT`"" 143 | "and startTime ge `"$($Date.ToString('yyyy-MM-dd'))T00:00:00.000Z`"" 144 | "and result.status eq `"FAILED`"" 145 | ) 146 | PS> $Activities = get-dmactivities -Filters $Filters -PageSize $PageSize 147 | 148 | .EXAMPLE 149 | PS> # GET ALL ACTIVITIES 150 | PS> $Activities = get-dmactivities -PageSize $PageSize 151 | 152 | .LINK 153 | https://developer.dell.com/apis/4378/versions/19.14.0/reference/ppdm-public.yaml/paths/~1api~1v2~1activities/get 154 | 155 | #> 156 | [CmdletBinding()] 157 | param ( 158 | [Parameter( Mandatory=$false)] 159 | [array]$Filters, 160 | [Parameter( Mandatory=$true)] 161 | [int]$PageSize 162 | ) 163 | begin {} 164 | process { 165 | $Results = @() 166 | $Endpoint = "activities" 167 | 168 | if($Filters.Length -gt 0) { 169 | $Join = ($Filters -join ' ') -replace '\s','%20' -replace '"','%22' 170 | $Endpoint = "$($Endpoint)?filter=$($Join)&pageSize=$($PageSize)" 171 | } else { 172 | $Endpoint = "$($Endpoint)?pageSize=$($PageSize)" 173 | } 174 | 175 | $Query = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&queryState=BEGIN" ` 176 | -Method GET ` 177 | -ContentType 'application/json' ` 178 | -Headers ($AuthObject.token) ` 179 | -SkipCertificateCheck 180 | $Results += $Query.content 181 | 182 | $Page = 1 183 | do { 184 | $Token = "$($Query.page.queryState)" 185 | if($Page -gt 1) { 186 | $Token = "$($Paging.page.queryState)" 187 | } 188 | $Paging = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&queryState=$($Token)" ` 189 | -Method GET ` 190 | -ContentType 'application/json' ` 191 | -Headers ($AuthObject.token) ` 192 | -SkipCertificateCheck 193 | $Results += $Paging.content 194 | 195 | $Page++; 196 | } 197 | until ($Paging.page.queryState -eq "END") 198 | return $Results 199 | } 200 | } 201 | 202 | function start-dmactivity { 203 | <# 204 | .SYNOPSIS 205 | Retry an activity 206 | 207 | .DESCRIPTION 208 | Retry an activity 209 | 210 | .EXAMPLE 211 | PS> retry-dmactivity -Id $Id 212 | 213 | .LINK 214 | https://developer.dell.com/apis/4378/versions/19.15.0/reference/ppdm-public.yaml/paths/~1api~1v2~1activities~1%7Bid%7D~1retry/post 215 | 216 | #> 217 | [CmdletBinding()] 218 | param ( 219 | [Parameter( Mandatory=$false)] 220 | [string]$Id 221 | ) 222 | begin {} 223 | process { 224 | 225 | $Endpoint = "activities/$($Id)/retry" 226 | # LOGOFF OF THE POWERPROTECT API 227 | $action = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)" ` 228 | -Method POST ` 229 | -ContentType 'application/json' ` 230 | -Headers ($AuthObject.token) ` 231 | -SkipCertificateCheck 232 | 233 | return $action 234 | } 235 | } # END FUNCTION 236 | 237 | <# 238 | WORKFLOW 239 | #> 240 | 241 | # CONNECT TO THE API 242 | connect-dmapi -Server $ppdm 243 | 244 | # QUERY FOR FAILED ACTIVITIES 245 | $Date = (Get-Date).AddDays(-$Days) 246 | $Filters = @( 247 | "classType eq `"JOB`"", 248 | "and category eq `"PROTECT`"", 249 | "and subcategory eq `"FULL`"", 250 | "and protectionPolicy.type eq `"MICROSOFT_SQL_DATABASE`"", 251 | "and result.status eq `"FAILED`"", 252 | "and startTime ge `"$($Date.ToString('yyyy-MM-ddThh:mm:ss.fffZ'))`"" 253 | ) 254 | 255 | $Hosts = @() 256 | $Query = get-dmactivities -Filters $Filters -PageSize $PageSize 257 | $Query | foreach-object { 258 | $Object = [ordered]@{ 259 | name = $_.host.Name 260 | parentId = $_.parentId 261 | errorCode = $_.result.error.code 262 | serviceacct = $user 263 | } 264 | $Hosts += (New-Object -TypeName psobject -Property $Object) 265 | } 266 | 267 | # GET THE LIST OF IMPACTED HOSTS AND THE ACTIVITY PARENT ID 268 | $Unique = $Hosts | select-Object name,parentId,errorCode,serviceacct -Unique 269 | 270 | Write-host "[INFO]: MSSQL hosts to remediate..." 271 | $Unique | format-table -AutoSize 272 | 273 | foreach($item in $Unique) { 274 | 275 | switch($item.errorCode) { 276 | 'ABS0016'{ 277 | try { 278 | Write-host "[INFO]: Querying local administrators on $($item.name) for user: $($item.serviceacct)" 279 | Invoke-Command -ComputerName $item.name -ScriptBlock { 280 | [CmdletBinding()] 281 | param ( 282 | [object]$Item 283 | ) 284 | $Item | format-list 285 | $member = Get-LocalGroupMember -Name 'Administrators' | ` 286 | where-object {$_.PrincipalSource -eq 'ActiveDirectory' -and $_.ObjectClass -eq 'User' -and $_.Name -eq $Item.serviceacct} 287 | 288 | if($member.length -eq 0 ) { 289 | Write-Host "[WARNING]: $($Item.serviceacct) not found. Adding them to the local administrators group.`n" -foregroundcolor Yellow 290 | Add-LocalGroupMember -Group Administrators -Member $Item.serviceacct 291 | 292 | Write-Host "[WARNING]: This is the user that broke your backups!...`n" -foregroundcolor Yellow 293 | Get-EventLog -LogName Security -InstanceId 4733 -Newest 1 | format-list 294 | } else { 295 | $member | format-list 296 | } 297 | } -ArgumentList $item 298 | } catch { 299 | $Error 300 | } 301 | 302 | Write-Host "[INFO]: Restarting failed activity: $($item.parentId)`n" -foregroundcolor Green 303 | $Retry = start-dmactivity -Id $item.parentId 304 | $Retry.retryResults.newJobId 305 | } 306 | 'ABS0018'{ 307 | Write-Host "[INFO]: We would do something else for this error code: $($_)" 308 | } 309 | } # END SWITCH 310 | } 311 | 312 | # DISCONNECT THE API 313 | disconnect-dmapi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /PowerShell7/Standalone/getLogs.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 3 | https://github.com/PowerShell/powershell/releases 4 | 5 | #> 6 | 7 | # VARS 8 | $Server = "ppdm-01.vcorp.local" 9 | $PageSize = 100 10 | $Poll = 10 11 | $Date = (get-date).AddHours(-7) 12 | $Path = ".\" 13 | $SqlHost = "win-sql-02.vcorp.local" 14 | 15 | # NOTHING TO CHANGE BELOW THIS LINE 16 | $global:ApiVersion = 'v2' 17 | $global:Port = 8443 18 | $global:AuthObject = $null 19 | 20 | function connect-dmapi { 21 | <# 22 | .SYNOPSIS 23 | Connect to the PowerProtect Data Manager REST API. 24 | 25 | .DESCRIPTION 26 | Creates a credentials file for PowerProtect Data Manager if one does not exist. 27 | Connects to the PowerProtect Data Manager REST API 28 | 29 | .PARAMETER Server 30 | Specifies the FQDN of the PowerProtect Data Manager server. 31 | 32 | .OUTPUTS 33 | System.Object 34 | $global:AuthObject 35 | 36 | .EXAMPLE 37 | PS> connect-ppdmapi -Server 'ppdm-01.vcorp.local' 38 | 39 | .LINK 40 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 41 | 42 | #> 43 | [CmdletBinding()] 44 | param ( 45 | [Parameter( Mandatory=$true)] 46 | [string]$Server 47 | ) 48 | begin { 49 | # CHECK TO SEE IF CREDS FILE EXISTS IF NOT CREATE ONE 50 | $Exists = Test-Path -Path ".\$($Server).xml" -PathType Leaf 51 | if($Exists) { 52 | $Credential = Import-CliXml ".\$($Server).xml" 53 | } else { 54 | $Credential = Get-Credential 55 | $Credential | Export-CliXml ".\$($Server).xml" 56 | } 57 | } 58 | process { 59 | $Login = @{ 60 | username="$($Credential.username)" 61 | password="$(ConvertFrom-SecureString -SecureString $Credential.password -AsPlainText)" 62 | } 63 | # LOGON TO THE POWERPROTECT API 64 | $Auth = Invoke-RestMethod -Uri "https://$($Server):$($Port)/api/$($ApiVersion)/login" ` 65 | -Method POST ` 66 | -ContentType 'application/json' ` 67 | -Body (ConvertTo-Json $Login) ` 68 | -SkipCertificateCheck 69 | $Object = @{ 70 | server ="https://$($Server):$($Port)/api/$($ApiVersion)" 71 | token= @{ 72 | authorization="Bearer $($Auth.access_token)" 73 | } #END TOKEN 74 | } #END AUTHOBJ 75 | 76 | $global:AuthObject = $Object 77 | 78 | $global:AuthObject | Format-List 79 | 80 | } #END PROCESS 81 | } #END FUNCTION 82 | 83 | function disconnect-dmapi { 84 | <# 85 | .SYNOPSIS 86 | Disconnect from the PowerProtect Data Manager REST API. 87 | 88 | .DESCRIPTION 89 | Destroys the bearer token contained with $global:AuthObject 90 | 91 | .OUTPUTS 92 | System.Object 93 | $global:AuthObject 94 | 95 | .EXAMPLE 96 | PS> disconnect-dmapi 97 | 98 | .LINK 99 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 100 | 101 | #> 102 | [CmdletBinding()] 103 | param ( 104 | ) 105 | begin {} 106 | process { 107 | #LOGOFF OF THE POWERPROTECT API 108 | Invoke-RestMethod -Uri "$($AuthObject.server)/logout" ` 109 | -Method POST ` 110 | -ContentType 'application/json' ` 111 | -Headers ($AuthObject.token) ` 112 | -SkipCertificateCheck 113 | 114 | $global:AuthObject = $null 115 | } 116 | } #END FUNCTION 117 | 118 | function get-dmactivities { 119 | <# 120 | .SYNOPSIS 121 | Get PowerProtect Data Manager activities 122 | 123 | .DESCRIPTION 124 | Get PowerProtect Data Manager activities based on filters 125 | 126 | .PARAMETER Filters 127 | An array of values used to filter the query 128 | 129 | .PARAMETER PageSize 130 | An int representing the desired number of elements per page 131 | 132 | .OUTPUTS 133 | System.Array 134 | 135 | .EXAMPLE 136 | PS> # GET ACTIVITIES BASED ON A FILTER 137 | PS> $Date = (Get-Date).AddDays(-1) 138 | PS> $Filters = @( 139 | "classType eq `"JOB`"" 140 | "and category eq `"PROTECT`"" 141 | "and startTime ge `"$($Date.ToString('yyyy-MM-dd'))T00:00:00.000Z`"" 142 | "and result.status eq `"FAILED`"" 143 | ) 144 | PS> $Activities = get-dmactivities -Filters $Filters -PageSize $PageSize 145 | 146 | .EXAMPLE 147 | PS> # GET ALL ACTIVITIES 148 | PS> $Activities = get-dmactivities -PageSize $PageSize 149 | 150 | .LINK 151 | https://developer.dell.com/apis/4378/versions/19.14.0/reference/ppdm-public.yaml/paths/~1api~1v2~1activities/get 152 | 153 | #> 154 | [CmdletBinding()] 155 | param ( 156 | [Parameter( Mandatory=$false)] 157 | [array]$Filters, 158 | [Parameter( Mandatory=$true)] 159 | [int]$PageSize 160 | ) 161 | begin {} 162 | process { 163 | $Results = @() 164 | $Endpoint = "activities" 165 | 166 | if($Filters.Length -gt 0) { 167 | $Join = ($Filters -join ' ') -replace '\s','%20' -replace '"','%22' 168 | $Endpoint = "$($Endpoint)?filter=$($Join)&pageSize=$($PageSize)" 169 | } else { 170 | $Endpoint = "$($Endpoint)?pageSize=$($PageSize)" 171 | } 172 | 173 | $Query = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&queryState=BEGIN" ` 174 | -Method GET ` 175 | -ContentType 'application/json' ` 176 | -Headers ($AuthObject.token) ` 177 | -SkipCertificateCheck 178 | $Results += $Query.content 179 | 180 | $Page = 1 181 | do { 182 | $Token = "$($Query.page.queryState)" 183 | if($Page -gt 1) { 184 | $Token = "$($Paging.page.queryState)" 185 | } 186 | $Paging = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&queryState=$($Token)" ` 187 | -Method GET ` 188 | -ContentType 'application/json' ` 189 | -Headers ($AuthObject.token) ` 190 | -SkipCertificateCheck 191 | $Results += $Paging.content 192 | 193 | $Page++; 194 | } 195 | until ($Paging.page.queryState -eq "END") 196 | return $Results 197 | } 198 | } 199 | 200 | function new-dmlogexport { 201 | <# 202 | .SYNOPSIS 203 | Start PowerProtect Data Manager backup 204 | 205 | .DESCRIPTION 206 | Start PowerProtect Data Manager backup either client or policy based 207 | 208 | .PARAMETER AssetIds 209 | An array of values asset ids to execute a backup against 210 | 211 | .PARAMETER Policy 212 | An object representing the defined policy 213 | 214 | .OUTPUTS 215 | System.Array 216 | 217 | .EXAMPLE 218 | PS> # Start a new log export job 219 | PS> $Export = new-dmlogexport -ActivityId $ActivityId 220 | 221 | 222 | #> 223 | [CmdletBinding()] 224 | param ( 225 | [Parameter( Mandatory=$false)] 226 | [array]$ActivityId 227 | ) 228 | begin {} 229 | process { 230 | 231 | $Endpoint = "log-exports" 232 | <# 233 | CREATE THE REQUEST BODY 234 | #> 235 | $Body = [ordered]@{ 236 | filterType = "ACTIVITY_ID" 237 | filterValue = "$($ActivityId)" 238 | } 239 | 240 | $Action = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)" ` 241 | -Method POST ` 242 | -ContentType 'application/json' ` 243 | -Headers ($AuthObject.token) ` 244 | -Body ($Body | convertto-json -Depth 10) ` 245 | -SkipCertificateCheck 246 | 247 | return $Action 248 | 249 | } # END PROCESS 250 | } 251 | 252 | function new-dmlogexportmonitor { 253 | <# 254 | .SYNOPSIS 255 | Get the latest copy for a PowerProtect Data Manager assets 256 | 257 | .DESCRIPTION 258 | Get the latest copy for a PowerProtect Data Manager assets 259 | 260 | .PARAMETER ActivityId 261 | A string representing the activity id you want to monitor 262 | 263 | .PARAMETER Poll 264 | an int representing the polling interval for the API 265 | 266 | .OUTPUTS 267 | System.Array 268 | 269 | .EXAMPLE 270 | # MONITOR THE EXPORT 271 | PS> $Filters = @( 272 | "id in (`"$($Export.id)`")" 273 | ) 274 | PS> new-dmlogexportmonitor -Filters $Filters -Poll $Poll 275 | 276 | .LINK 277 | https://developer.dell.com/apis/4378/versions/19.14.0/reference/ppdm-public.yaml/paths/~1api~1v2~1activities~1%7Bid%7D/get 278 | 279 | #> 280 | [CmdletBinding()] 281 | param ( 282 | [Parameter( Mandatory=$false)] 283 | [array]$Filters, 284 | [Parameter( Mandatory=$true)] 285 | [int]$Poll 286 | ) 287 | begin {} 288 | process { 289 | $Endpoint = "log-exports" 290 | if($Filters.Length -gt 0) { 291 | $Join = ($Filters -join ' ') -replace '\s','%20' -replace '"','%22' 292 | $Endpoint = "$($Endpoint)?filter=$($Join)" 293 | } else { 294 | $Endpoint = "$($Endpoint)" 295 | } 296 | 297 | do { 298 | #POLL THE RECOVERY ACTIVITY EVERY 60 SECONDS UNTIL COMPLETE 299 | 300 | $Content = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)" ` 301 | -Method GET ` 302 | -ContentType 'application/json' ` 303 | -Headers ($AuthObject.token) ` 304 | -SkipCertificateCheck 305 | 306 | $Monitor = $Content.content 307 | if($Monitor.status -ne "COLLECTED"){ 308 | Write-Host "[ACTIVITY]: $($Monitor.logExportTargetActivityId), Status = $($Monitor.status), Progress: $($Monitor.progress), Sleeping $($Poll) seconds..." -ForegroundColor Yellow 309 | Start-Sleep -Seconds $Poll 310 | } else { 311 | Write-Host "[ACTIVITY]: $($Monitor.logExportTargetActivityId), Status = $($Monitor.status), Progress: $($Monitor.progress)`n" -ForegroundColor Green 312 | } 313 | } until($Monitor.status -eq "COLLECTED") 314 | } 315 | } 316 | 317 | function get-dmlogexportfile { 318 | <# 319 | .SYNOPSIS 320 | Start PowerProtect Data Manager backup 321 | 322 | .DESCRIPTION 323 | Start PowerProtect Data Manager backup either client or policy based 324 | 325 | .PARAMETER AssetIds 326 | An array of values asset ids to execute a backup against 327 | 328 | .PARAMETER Policy 329 | An object representing the defined policy 330 | 331 | .OUTPUTS 332 | System.Array 333 | 334 | .EXAMPLE 335 | PS> # Download the exported logs 336 | PS> get-dmlogexportfile -ActivityId $ActivityId -Path $Path 337 | 338 | .EXAMPLE 339 | PS> # Start backup for defined clients 340 | PS> $Backup = new-dmbackup -Clients $Clients -Policy $Policy -PageSize 100 341 | 342 | #> 343 | [CmdletBinding()] 344 | param ( 345 | [Parameter( Mandatory=$false)] 346 | [array]$ActivityId, 347 | [Parameter( Mandatory=$false)] 348 | [String]$Path 349 | 350 | ) 351 | begin {} 352 | process { 353 | 354 | $Endpoint = "log-exports/$($ActivityId)/file" 355 | <# 356 | CREATE THE REQUEST BODY 357 | #> 358 | 359 | $Action = Invoke-WebRequest -Uri "$($AuthObject.server)/$($Endpoint)" ` 360 | -Method GET ` 361 | -ContentType 'application/json' ` 362 | -Headers ($AuthObject.token) ` 363 | -SkipCertificateCheck 364 | 365 | $Content = $Action.Headers.'Content-Disposition' 366 | $FileName = $Content -split('=') | Select-Object -Last 1 367 | 368 | $File = [System.IO.FileStream]::new("$($Path)\$($FileName)", [System.IO.FileMode]::Create) 369 | $File.write($Action.Content, 0, $Action.RawContentLength) 370 | $File.close() 371 | 372 | 373 | } # END PROCESS 374 | } 375 | # CONNECT TO THE REST API 376 | connect-dmapi -Server $Server 377 | 378 | $Filters = @( 379 | "category eq `"PROTECT`"", 380 | "and classType eq `"JOB`"", 381 | "and subcategory eq `"FULL`"", 382 | "and state eq `"COMPLETED`"", 383 | "and host.name eq `"$($SqlHost)`"", 384 | "and startTime gt `"$($Date.ToString('o'))`"" 385 | ) 386 | # GET ASSETS BASED ON FILTERS 387 | $Activities = get-dmactivities -Filters $Filters -PageSize $PageSize 388 | 389 | $Activities | foreach-object { 390 | Write-Host "[$($Server)]: Exporting logs for $($SqlHost)\$($_.asset.name)" 391 | # GENERATE THE EXPORT 392 | $Export = new-dmlogexport -ActivityId $_.id 393 | 394 | # MONITOR THE EXPORT 395 | $Filters = @( 396 | "id in (`"$($Export.id)`")" 397 | ) 398 | new-dmlogexportmonitor -Filters $Filters -Poll $Poll 399 | 400 | # DOWNLOAD THE EXPORT 401 | get-dmlogexportfile -ActivityId $_.id -Path $Path 402 | } 403 | 404 | # DISCONNECT FROM THE REST API 405 | disconnect-dmapi -------------------------------------------------------------------------------- /Python/ppdm_exclude_pvc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import sys 5 | import json 6 | import subprocess 7 | import requests 8 | import urllib3 9 | 10 | if "native" not in sys.argv: 11 | try: 12 | from kubernetes import config, dynamic 13 | from kubernetes.client import api_client 14 | NATIVE_API = False 15 | except (ImportError, ModuleNotFoundError): 16 | NATIVE_API = True 17 | 18 | # Automates PVC exclusion in PowerProtect Data Manager k8s data protection 19 | # Author - Idan Kentor 20 | # Version 1 - April 2024 21 | # Copyright [2024] [Idan Kentor] 22 | 23 | # Examples: 24 | # python ppdm_exclude_pvc.py -ppdm 10.0.0.1 -p MyTempPwd123! -a exclude -pvc mysql1 -ns mysql 25 | # python ppdm_exclude_pvc.py -ppdm 10.0.0.1 -p MyTempPwd123! -a include -pvc maria -ns maria -native 26 | # python ppdm_exclude_pvc.py -ppdm 10.0.0.1 -p MyTempPwd123! -a batch 27 | # python ppdm_exclude_pvc.py -ppdm 10.0.0.1 -p MyTempPwd123! -a batch -native 28 | # python ppdm_exclude_pvc.py -ppdm 10.0.0.1 -p MyTempPwd123! -a list -cl k8s_prod1 29 | 30 | 31 | urllib3.disable_warnings() 32 | 33 | 34 | def get_args(): 35 | """Gets command line args from the user""" 36 | parser = argparse.ArgumentParser( 37 | description="Automate PVC exclusion in Dell PPDM k8s data protection" 38 | ) 39 | parser.add_argument( 40 | "-ppdm", 41 | "--ppdm", 42 | required=False, 43 | dest="ppdm", 44 | action="store", 45 | help="Specify the PPDM server FQDN or IP ", 46 | ) 47 | parser.add_argument( 48 | "-u", 49 | "--username", 50 | required=False, 51 | dest="username", 52 | action="store", 53 | default="admin", 54 | help="Optionally provide the PPDM username", 55 | ) 56 | parser.add_argument( 57 | "-p", 58 | "--password", 59 | required=True, 60 | dest="password", 61 | action="store", 62 | help="PPDM password", 63 | ) 64 | parser.add_argument( 65 | "-a", 66 | "--action", 67 | required=True, 68 | choices=["exclude", "include", "batch", "list"], 69 | help='List PVCs, exclude/include a specific PVC or exclude multiple PVCs' 70 | ) 71 | parser.add_argument( 72 | "-pvc", 73 | "--volume-name", 74 | required=False, 75 | dest="pvc", 76 | action="store", 77 | help="Optionally provide the PVC volume name", 78 | ) 79 | parser.add_argument( 80 | "-ns", 81 | "--namespace", 82 | required=False, 83 | dest="ns", 84 | action="store", 85 | help="Optionally provide the relevant namespace", 86 | ) 87 | parser.add_argument( 88 | "-cl", 89 | "--cluster", 90 | required=False, 91 | dest="cluster", 92 | action="store", 93 | help="Optionally filter listings to a specific k8s cluster", 94 | ) 95 | parser.add_argument( 96 | "-native", 97 | "--native", 98 | required=False, 99 | dest="native", 100 | action="store_true", 101 | help="Optionally use native kubectl", 102 | ) 103 | 104 | args = parser.parse_args() 105 | return args 106 | 107 | 108 | def get_volume_details(ns=None, pvc_name=None, annotation=None): 109 | """Get PVC details via Kubernetes module""" 110 | client = dynamic.DynamicClient(api_client.ApiClient(configuration=config.load_kube_config())) 111 | api = client.resources.get(api_version="v1", kind="PersistentVolumeClaim") 112 | exclude_pvcs = [] 113 | if ns: 114 | pvcs = api.get(namespace=ns) 115 | else: 116 | pvcs = api.get() 117 | for pvc in pvcs.items: 118 | if pvc_name: 119 | if pvc_name == pvc.metadata.name: 120 | return pvc.spec.volumeName 121 | if annotation: 122 | for label in pvc.metadata.annotations: 123 | if label[0] == annotation: 124 | if label[1] in ("yes", "no"): 125 | exclude_pvcs.append({pvc.spec.volumeName: label[1]}) 126 | if exclude_pvcs: 127 | return exclude_pvcs 128 | return False 129 | 130 | 131 | def get_volume_details_native(ns=None, pvc_name=None, annotation=None): 132 | """Get PVC details via kubectl""" 133 | exclude_pvcs = [] 134 | if ns: 135 | command = f"kubectl get pvc -n {ns} -o json" 136 | else: 137 | command = "kubectl get pvc -A -o json" 138 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 139 | pvcs, error = process.communicate() 140 | if not pvcs or error: 141 | raise SystemExit("no PVCs detected, Existing...") 142 | pvcs = json.loads(pvcs) 143 | for pvc in pvcs["items"]: 144 | if pvc_name: 145 | if pvc_name == pvc["metadata"]["name"]: 146 | return pvc["spec"]["volumeName"] 147 | if annotation: 148 | for label in pvc["metadata"]["annotations"]: 149 | if label[0] == annotation: 150 | if label[1] == "yes": 151 | exclude_pvcs.append({pvc["spec"]["volumeName"]: label[1]}) 152 | if exclude_pvcs: 153 | return exclude_pvcs 154 | return False 155 | 156 | 157 | def determine_asset_id(uri, token, pvc_volname): 158 | """Determines the asset ID based on PVC volume name""" 159 | uri = f"{uri}/assets" 160 | query = 'type eq "KUBERNETES" and subtype eq "K8S_PERSISTENT_VOLUME_CLAIM"' 161 | pvc_query = f'{query} and details.k8s.persistentVolumeClaim.volumeName eq "{pvc_volname}"' 162 | params = {"filter": pvc_query} 163 | response = init_rest_call("GET", uri, token, None, params) 164 | if not response: 165 | return False 166 | if len(response["content"]) == 1: 167 | return response["content"][0]["id"] 168 | if len(response["content"]) == 0: 169 | pvc_uid = pvc_volname.split("pvc-")[1] 170 | pvc_query = f'{query} and details.k8s.uid eq "{pvc_uid}"' 171 | params = {"filter": pvc_query} 172 | response = init_rest_call("GET", uri, token, None, params) 173 | if len(response["content"]) == 1: 174 | return response["content"][0]["id"] 175 | return False 176 | 177 | 178 | def list_pvc_volumes(uri, token, cluster=None): 179 | """Lists all PVC volumes""" 180 | uri = f"{uri}/assets" 181 | query = 'type eq "KUBERNETES" and subtype eq "K8S_PERSISTENT_VOLUME_CLAIM"' 182 | params = {"filter": query} 183 | response = init_rest_call("GET", uri, token, None, params) 184 | if not response: 185 | return False 186 | if len(response["content"]) == 0: 187 | return False 188 | pvc_volumes = [] 189 | for pvc in response["content"]: 190 | pvc_details = pvc["details"]["k8s"] 191 | if cluster: 192 | if pvc_details["inventorySourceName"] != cluster: 193 | continue 194 | pvc_report = { 195 | "name": pvc["name"], 196 | "assetID": pvc["id"], 197 | "pvcID": f"pvc-{pvc_details['uid']}", 198 | "namespace": pvc_details["namespace"], 199 | "AssetSource": pvc_details["inventorySourceName"], 200 | "protectionStatus": pvc["protectionStatus"], 201 | "protectionPolicy": pvc["protectionPolicy"]["name"], 202 | "sizeInGB": pvc["size"] / (1024**3), 203 | "excluded": pvc_details["persistentVolumeClaim"]["excluded"], 204 | "storageClass": pvc_details["persistentVolumeClaim"]["storageClassName"], 205 | "accessModes": pvc_details["persistentVolumeClaim"]["accessModes"], 206 | "CreationDate": pvc_details["externalCreatedAt"], 207 | "deleted": pvc["deleted"] 208 | } 209 | pvc_volumes.append(pvc_report) 210 | return pvc_volumes 211 | 212 | 213 | def exclude_pvc(uri, token, asset_id, exclude): 214 | """Excludes/includes PVC based on asset ID""" 215 | uri = f"{uri}/assets/{asset_id}" 216 | payload = { 217 | "id": asset_id, 218 | "details": {"k8s": {"persistentVolumeClaim": {"excluded": exclude}}} 219 | } 220 | response = init_rest_call("PATCH", uri, token, payload) 221 | if response: 222 | if exclude: 223 | action = "excluded" 224 | else: 225 | action = "included" 226 | print(f"Asset ID {asset_id} was {action} successfully") 227 | return response 228 | 229 | 230 | def init_rest_call(verb, uri, token, payload=None, params=None): 231 | """Generic function for REST calls""" 232 | if uri.endswith("/login"): 233 | headers = {"Content-Type": "application/json"} 234 | else: 235 | headers = { 236 | "Content-Type": "application/json", 237 | "Authorization": "Bearer " f"{token}", 238 | } 239 | payload = json.dumps(payload) 240 | codes = {200, 201, 202, 204} 241 | verify = False 242 | timeout = 90 243 | try: 244 | if verb.lower() == "get": 245 | response = requests.get( 246 | uri, 247 | headers=headers, 248 | params=params, 249 | verify=verify, 250 | timeout=timeout 251 | ) 252 | else: 253 | response = requests.request( 254 | verb, 255 | uri, 256 | headers=headers, 257 | params=params, 258 | data=payload, 259 | verify=verify, 260 | timeout=timeout, 261 | ) 262 | response.raise_for_status() 263 | except requests.exceptions.ConnectionError as error: 264 | print(f"-> Error Connecting to {uri}: {error}") 265 | raise SystemExit(1) from error 266 | except requests.exceptions.Timeout as error: 267 | print(f"-> Connection timed out {urllib3}: {error}") 268 | raise SystemExit(1) from error 269 | except requests.exceptions.RequestException as error: 270 | if response.status_code in (401, 502): 271 | return False 272 | print(f"{response.request.method} {response.url} failed with {error}") 273 | if uri.endswith("/login"): 274 | return response.json()["access_token"] 275 | if response.status_code not in codes: 276 | return False 277 | try: 278 | return response.json() 279 | except json.decoder.JSONDecodeError: 280 | if response.status_code == 204: 281 | return True 282 | return response.text 283 | 284 | 285 | def authenticate(uri, username, password): 286 | """Login""" 287 | uri = f"{uri}/login" 288 | payload = {"username": username, "password": password} 289 | token = init_rest_call("POST", uri, payload, payload) 290 | return token 291 | 292 | 293 | def main(): 294 | # Args assignment 295 | 296 | args = get_args() 297 | ppdm, username, password = args.ppdm, args.username, args.password 298 | action, pvc, ns, cluster = args.action, args.pvc, args.ns, args.cluster 299 | if NATIVE_API: 300 | native = True 301 | else: 302 | native = args.native 303 | 304 | # Const definition 305 | annotation = "ppdm.config.exclude/pvc" 306 | api_endpoint = "/api/v2" 307 | api_port = 8443 308 | 309 | # Logs into the PPDM API 310 | uri = f"https://{ppdm}:{api_port}{api_endpoint}" 311 | token = authenticate(uri, username, password) 312 | 313 | if action == "exclude" or action == "include": 314 | exclude = bool(action == "exclude") 315 | if native: 316 | pvc_volname = get_volume_details_native(ns, pvc) 317 | else: 318 | pvc_volname = get_volume_details(ns, pvc) 319 | if pvc_volname: 320 | asset_id = determine_asset_id(uri, token, pvc_volname) 321 | if not asset_id: 322 | raise SystemExit(f"Could not find volume {pvc}") 323 | exclude_pvc(uri, token, asset_id, exclude) 324 | else: 325 | raise SystemExit(f"Could not find volume {pvc}") 326 | elif action == "batch": 327 | if native: 328 | exclude_pvcs = get_volume_details_native(ns, None, annotation) 329 | else: 330 | exclude_pvcs = get_volume_details(ns, None, annotation) 331 | if exclude_pvcs: 332 | for pvc in exclude_pvcs: 333 | for pvc_volname, exclude in pvc.items(): 334 | asset_id = determine_asset_id(uri, token, pvc_volname) 335 | if not asset_id: 336 | raise SystemExit("No volumes found for exclusion") 337 | exclude = bool(exclude == "yes") 338 | exclude_pvc(uri, token, asset_id, exclude) 339 | else: 340 | raise SystemExit("No volumes found for exclusion") 341 | elif action == "list": 342 | pvc_volumes = list_pvc_volumes(uri, token, cluster) 343 | if not pvc_volumes: 344 | raise SystemExit("No volumes found") 345 | print(f"Total: {len(pvc_volumes)} PVCs") 346 | print(json.dumps(pvc_volumes, indent=4)) 347 | print("-> All tasks have been completed") 348 | 349 | 350 | if __name__ == "__main__": 351 | main() 352 | -------------------------------------------------------------------------------- /Python/filelevelrestore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import urllib3 6 | import json 7 | import sys 8 | import time 9 | from datetime import datetime 10 | 11 | # Perform File Level Recovery in PowerProtect 12 | 13 | urllib3.disable_warnings() 14 | 15 | def get_args(): 16 | # Get command line args from the user 17 | parser = argparse.ArgumentParser( 18 | description='Script to update Protection Policy Schedule in PowerProtect Data Manager') 19 | parser.add_argument('-s', '--server', required=True, 20 | action='store', help='PPDM DNS name or IP') 21 | parser.add_argument('-usr', '--user', required=False, action='store', 22 | default='admin', help='User') 23 | parser.add_argument('-pwd', '--password', required=True, action='store', 24 | help='Password') 25 | parser.add_argument('-name', '--vmname', required=True, action='store', 26 | help='Provide the name of the protected VM') 27 | parser.add_argument('-action', '--action', required=True, choices=['list_files', 'recover_files'], 28 | help='Choose to list or recover files') 29 | parser.add_argument('-dir', '--dir', required=('list_files' in sys.argv), action='store', 30 | help='The directory to list the files under it.') 31 | parser.add_argument('-files', '--filenames', required=('recover_files' in sys.argv), action='store', 32 | help='Provide the names of the files(comma separated) to be restored') 33 | parser.add_argument('-overwrite', '--overwrite', required=('recover_files' in sys.argv), choices=['yes', 'no'], 34 | help='Choose to overwrite existing files or not') 35 | parser.add_argument('-targetuser', '--targetuser', required=True, action='store', 36 | help='Provide the username of the target VM') 37 | parser.add_argument('-targetpwd', '--targetpassword', required=True, action='store', 38 | help='Provide the password of the target VM') 39 | args = parser.parse_args() 40 | return args 41 | 42 | def authenticate(ppdm, user, password, uri): 43 | # Login 44 | print('\nRetrieving authorization token') 45 | loginendpoint = '/login' 46 | uri += loginendpoint 47 | headers = {'Content-Type': 'application/json'} 48 | payload = '{"username": "%s", "password": "%s"}' % (user, password) 49 | 50 | try: 51 | response = requests.post( 52 | uri, data=payload, headers=headers, verify=False) 53 | response.raise_for_status() 54 | if (response.status_code != 200): 55 | raise Exception('Login failed for user: {}, code: {}, body: {}'.format( 56 | user, response.status_code, response.text)) 57 | print('\nUser: {} logged in to PPDM: {}'.format(user, ppdm)) 58 | # Parse the response and extract the access_token 59 | accesstoken = response.json()['access_token'] 60 | return accesstoken 61 | except requests.exceptions.ConnectionError as errc: 62 | print('\nError Connecting to {}: {}'.format(ppdm, errc)) 63 | sys.exit() 64 | except requests.exceptions.Timeout as errt: 65 | print('\nConnection timed out, make sure that there is connectivity to {}: {}'.format( 66 | ppdm, errt)) 67 | sys.exit() 68 | except requests.exceptions.RequestException: 69 | print('\nThe call {} {} failed with exception:{}'.format( 70 | response.request.method, response.url, response.text)) 71 | sys.exit() 72 | 73 | def get_asset_details(uri, accesstoken, assetname): 74 | # Get asset id using the type and name 75 | print('\nFetching Virtual Machine using name: {}'.format(assetname)) 76 | assetsendpoint = '/assets' 77 | assetsapifilter = '?filter=type%20eq%20%22VMWARE_VIRTUAL_MACHINE%22%20and%20name%20lk%20%22%25'+assetname+'%25%22' 78 | uri = uri + assetsendpoint + assetsapifilter 79 | headers = {'Content-Type': 'application/json', 80 | 'Authorization': 'Bearer {}'.format(accesstoken)} 81 | try: 82 | response = requests.get(uri, headers=headers, verify=False) 83 | response.raise_for_status() 84 | if (response.status_code != 200): 85 | raise Exception('\nCould not fetch asset details: , code: {}, body: {}'.format( 86 | response.status_code, response.text)) 87 | if len(response.json()['content']) == 0: 88 | print('\nNo asset found matching criteria - name: {}'.format(assetname)) 89 | sys.exit() 90 | elif len(response.json()['content']) > 1: 91 | print( 92 | '\nMultiple assets found matching criteria. Please refine the search further.') 93 | sys.exit() 94 | else: 95 | assetid = response.json()['content'][0]['id'] 96 | assetname = response.json()['content'][0]['name'] 97 | print('\nAsset id = {}, Asset name: {}'.format(assetid, assetname)) 98 | return assetid, assetname 99 | except requests.exceptions.RequestException: 100 | print('\nThe call {}{} failed with exception:{}'.format( 101 | response.request.method, response.url, response.text)) 102 | sys.exit() 103 | 104 | def get_latest_copy(uri, accesstoken, assetid, assetname): 105 | # Fetch the backup copies available for the asset 106 | print('\nFetching available backup copies for asset: {}'.format(assetname)) 107 | copiesendpoint = '/assets/' + assetid + '/copies' 108 | copiesapifilter = '?orderby=createTime%20DESC' 109 | uri += copiesendpoint + copiesapifilter 110 | headers = {'Content-Type': 'application/json', 111 | 'Authorization': 'Bearer {}'.format(accesstoken)} 112 | try: 113 | # Get the latest copy 114 | response = requests.get(uri, headers=headers, verify=False) 115 | response.raise_for_status() 116 | if (response.status_code != 200): 117 | raise Exception('\nFailed to get copies for asset: {}, code: {}, body: {}'.format( 118 | assetname, response.status_code, response.text)) 119 | if response.json()['content']: 120 | copyid = response.json()['content'][0]['id'] 121 | print('\nLatest Backup Copy id: {}, Created at {}'.format( 122 | copyid, response.json()['content'][0]['createTime'])) 123 | return copyid 124 | else: 125 | print("\nNo backup copies found for the asset: {}".format(assetname)) 126 | sys.exit() 127 | except requests.exceptions.RequestException: 128 | print('\nThe call {} {} failed with exception:{}'.format( 129 | response.request.method, response.url, response.text)) 130 | sys.exit() 131 | 132 | def create_flr_session(uri, accesstoken, assetid, copyid, targetuser, targetpassword): 133 | # Create an FLR session 134 | print('\nCreating an FLR session') 135 | flrsessionendpoint = '/flr-sessions' 136 | uri += flrsessionendpoint 137 | headers = {'Content-Type': 'application/json', 138 | 'Authorization': 'Bearer {}'.format(accesstoken)} 139 | payload = json.dumps({ 140 | 'copyId': copyid, 141 | 'removeAgent': 'true', 142 | 'targetPassword': targetpassword, 143 | 'targetUser': targetuser, 144 | 'targetVmAssetId': assetid, 145 | 'timeout': 300 146 | }) 147 | try: 148 | response = requests.post(uri, headers=headers, 149 | data=payload, verify=False) 150 | response.raise_for_status() 151 | if response.status_code not in [200, 201, 202]: 152 | raise Exception('\nFailed to create FLR session, code: {}, body: {}'.format(response.status_code, response.text)) 153 | # Creating of an FLR session is an asynchronous operation. So, get the activity id to monitor the progress. 154 | activityid = get_activity_id_from_response(response.json()) 155 | flrsessionid = response.json()['flrSessionId'] 156 | print('\nFLR session creation - Activity id: {}'.format(activityid)) 157 | return activityid, flrsessionid 158 | except requests.exceptions.RequestException: 159 | print('\nThe call {} {} failed with exception:{}'.format( 160 | response.request.method, response.url, response.text)) 161 | sys.exit() 162 | 163 | def get_activity_id_from_response(response): 164 | # Parse the activity id from the response body 165 | if 'activityId' in response: 166 | return response['activityId'] 167 | if 'taskId' in response: 168 | return response['taskId'] 169 | if 'jobId' in response: 170 | return response['jobId'] 171 | return None 172 | 173 | def monitor_task_status(uri, accesstoken, activityid): 174 | # Monitor the creation of the FLR session 175 | print('\nMonitoring activity: {}'.format(activityid)) 176 | monitoractivityendpoint = '/activities/' + activityid 177 | uri += monitoractivityendpoint 178 | headers = {'Content-Type': 'application/json', 179 | 'Authorization': 'Bearer {}'.format(accesstoken)} 180 | sleepinterval = 10 181 | timeout = 300 182 | # Monitor the task till it succeeds, fails or times out 183 | starttime = datetime.now() 184 | while True: 185 | if (datetime.now() - starttime).total_seconds() > timeout: 186 | break 187 | try: 188 | response = requests.get(uri, headers=headers, verify=False) 189 | response.raise_for_status() 190 | if (response.status_code != 200): 191 | raise Exception('\nUnable to monitor task: {}, code: {}, body: {}'.format( 192 | uri, response.status_code, response.text)) 193 | # Check for success status 194 | taskstatus = response.json()['state'] 195 | print('\nTask status: {}'.format(taskstatus)) 196 | if taskstatus == 'COMPLETED': 197 | return response.json()['result']['status'], response.json() 198 | time.sleep(sleepinterval) 199 | except requests.exceptions.RequestException: 200 | print('\nThe call {} {} failed with exception:{}'.format( 201 | response.request.method, response.url, response.text)) 202 | sys.exit() 203 | return 'TIMEOUT', response.json() 204 | 205 | def get_directory_file_list(uri, accesstoken, flrsessionid, dir): 206 | # Get the directory file list 207 | filelistendpoint = '/flr-sessions/' + flrsessionid + '/files' 208 | getfilelisturi = uri + filelistendpoint 209 | headers = {'Content-Type': 'application/json', 210 | 'Authorization': 'Bearer {}'.format(accesstoken)} 211 | try: 212 | change_directory(uri, accesstoken, flrsessionid, dir) 213 | print('\nGetting the directory file list') 214 | response = requests.get(getfilelisturi, headers=headers, verify=False) 215 | response.raise_for_status() 216 | if (response.status_code != 200): 217 | raise Exception('\nFailed to get file list, code: {}, body: {}'.format( 218 | response.status_code, response.text)) 219 | print(response.text) 220 | except requests.exceptions.RequestException: 221 | print('\nThe call {} {} failed with exception:{}'.format( 222 | response.request.method, response.url, response.text)) 223 | sys.exit() 224 | 225 | def change_directory(uri, accesstoken, flrsessionid, dir): 226 | # Changes Directory for list listing 227 | print('\nChanging directory') 228 | urllib3.disable_warnings() 229 | dirchangeendpoint = '/flr-sessions/' + flrsessionid 230 | changediruri = uri + dirchangeendpoint 231 | headers = {'Content-Type': 'application/json', 232 | 'Authorization': 'Bearer {}'.format(accesstoken)} 233 | payload = json.dumps({ 234 | "browseDest": "false", 235 | "directory": dir 236 | }) 237 | try: 238 | response = requests.put(changediruri, data=payload, headers=headers, verify=False) 239 | response.raise_for_status() 240 | if (response.status_code != 200): 241 | raise Exception('\nFailed to change directory to: {}, code: {}, body: {}'.format( 242 | dir, response.status_code, response.text)) 243 | print(response.text) 244 | except requests.exceptions.RequestException: 245 | print('\nThe call {} {} failed with exception:{}'.format( 246 | response.request.method, response.url, response.text)) 247 | delete_flr_session(uri, accesstoken, flrsessionid) 248 | sys.exit() 249 | 250 | def restore_files(uri, accesstoken, flrsessionid, filenames, overwrite): 251 | # Restore specified files 252 | print('\nRestoring the specified files') 253 | urllib3.disable_warnings() 254 | restorefilesendpoint = '/flr-sessions/' + flrsessionid + '/tasks' 255 | restorefilesuri = uri + restorefilesendpoint 256 | headers = {'Content-Type': 'application/json', 257 | 'Authorization': 'Bearer {}'.format(accesstoken)} 258 | payload = json.dumps({ 259 | "filePaths": filenames.split(','), 260 | "overwriteExisting": overwrite, 261 | "restoreToOriginalPath": "true", 262 | "targetDirectory": "" 263 | }) 264 | try: 265 | print(filenames) 266 | response = requests.post(restorefilesuri, headers=headers, data=payload, verify=False) 267 | response.raise_for_status() 268 | if response.status_code not in [200, 201, 202]: 269 | raise Exception('\nFailed to restore files, code: {}, body: {}'.format(response.status_code, response.text)) 270 | # Restoring a file is an asynchronous operation. So, get the task URL to monitor the progress. 271 | activityid = get_activity_id_from_response(response.json()) 272 | print('\nRestore file(s) - activity id: {}'.format(activityid)) 273 | return activityid 274 | except requests.exceptions.RequestException: 275 | print('\nThe call {} {} failed with exception:{}'.format( 276 | response.request.method, response.url, response.text)) 277 | delete_flr_session(uri, accesstoken, flrsessionid) 278 | sys.exit() 279 | 280 | def delete_flr_session(uri, accesstoken, flrsessionid): 281 | # Deleting the FLR session 282 | print('\nDeleting the FLR session') 283 | deleteflrsessionendpoint = '/flr-sessions/' + flrsessionid 284 | deleteflrsessionuri = uri + deleteflrsessionendpoint 285 | headers = {'Content-Type': 'application/json', 286 | 'Authorization': 'Bearer {}'.format(accesstoken)} 287 | try: 288 | response = requests.delete(deleteflrsessionuri, headers=headers, verify=False) 289 | response.raise_for_status() 290 | if response.status_code not in [200, 202, 204]: 291 | raise Exception('\nFailed to delete FLR session: {}, code: {}, body: {}'.format( 292 | flrsessionid, response.status_code, response.text)) 293 | activityid = get_activity_id_from_response(response.json()) 294 | print('\nDelete FLR session - activity id: {}'.format(activityid)) 295 | status, response = monitor_task_status(uri, accesstoken, activityid) 296 | print('\nDelete FLR session - status: {}'.format(status)) 297 | except requests.exceptions.RequestException: 298 | print('\nThe call {} {} failed with exception:{}'.format( 299 | response.request.method, response.url, response.text)) 300 | sys.exit() 301 | 302 | def logout(ppdm, user, uri, accesstoken): 303 | # Logs out of PowerProtect 304 | print('\nLogging out of the current session') 305 | logoutendpoint = '/logout' 306 | uri += logoutendpoint 307 | headers = {'Content-Type': 'application/json', 308 | 'Authorization': 'Bearer {}'.format(accesstoken)} 309 | try: 310 | response = requests.post( 311 | uri, headers=headers, verify=False) 312 | response.raise_for_status() 313 | if (response.status_code != 204): 314 | raise Exception('\nLogout failed for user: {}, code: {}, body: {}'.format( 315 | user, response.status_code, response.text)) 316 | print('\nUser: {} logged out of PPDM: {}'.format(user, ppdm)) 317 | except requests.exceptions.RequestException: 318 | print('\nThe call {} {} failed with exception:{}'.format( 319 | response.request.method, response.url, response.text)) 320 | sys.exit() 321 | 322 | def main(): 323 | port = '8443' 324 | apiendpoint = '/api/v2' 325 | args = get_args() 326 | ppdm = args.ppdm 327 | user = args.user 328 | password = args.password 329 | vmname = args.vmname 330 | filenames = args.filenames 331 | targetuser = args.targetuser 332 | targetpassword = args.targetpassword 333 | action = args.action 334 | overwrite = False if args.overwrite == 'no' else True 335 | dir = args.dir 336 | uri = 'https://{}:{}{}'.format(ppdm, port, apiendpoint) 337 | accesstoken = authenticate(ppdm, user, password, uri) 338 | assetid, assetname = get_asset_details(uri, accesstoken, vmname) 339 | copyid = get_latest_copy(uri, accesstoken, assetid, assetname) 340 | activityid, flrsessionid = create_flr_session( 341 | uri, accesstoken, assetid, copyid, targetuser, targetpassword) 342 | status, response = monitor_task_status(uri, accesstoken, activityid) 343 | if status == 'OK': 344 | if 'list_files' == action: 345 | if status == 'OK': 346 | get_directory_file_list(uri, accesstoken, flrsessionid, dir) 347 | delete_flr_session(uri, accesstoken, flrsessionid) 348 | else: 349 | if status == 'OK': 350 | activityid = restore_files( 351 | uri, accesstoken, flrsessionid, filenames, overwrite) 352 | status, response = monitor_task_status(uri, accesstoken, activityid) 353 | # Restore files API will automatically delete the FLR session. 354 | # delete_flr_session(uri, accesstoken, flrsessionid) 355 | else: 356 | print('\nFLR session could not be created, error: {}'.format(response['result']['error']['reason'])) 357 | logout(ppdm, user, uri, accesstoken) 358 | 359 | if __name__ == "__main__": 360 | main() 361 | -------------------------------------------------------------------------------- /PowerShell7/Standalone/getNasFileReport.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter( Mandatory=$false)] 4 | [switch]$PDF 5 | ) 6 | <# 7 | THIS CODE REQUIRES POWWERSHELL 7.x.(latest) 8 | https://github.com/PowerShell/powershell/releases 9 | 10 | IMPORT THE EXCEL INTEROP ASSEMBLY 11 | I HAD TO DROP THIS ASSEMBLY IN THE SCRIPT FOLDER FROM HERE: 12 | C:\Program Files\Microsoft Office\root\vfs\ProgramFilesX86\Microsoft Office\Office16\DCF 13 | #> 14 | Add-Type -AssemblyName Microsoft.Office.Interop.Excel 15 | # .NET ASSEMPLY FOR IMAGES 16 | Add-Type -AssemblyName System.Drawing 17 | 18 | # GLOBAL VARS 19 | $global:ApiVersion = 'v2' 20 | $global:Port = 8443 21 | $global:AuthObject = $null 22 | 23 | # VARS 24 | $Servers = @( 25 | "10.239.100.131" 26 | ) 27 | $Retires = @(1..5) 28 | $Seconds = 10 29 | $PageSize = 100 30 | 31 | # REPORT OPTIONS 32 | $ReportName = "NasFileReport" 33 | $ReportOutPath = "C:\Reports\output" 34 | $ReportOutFile = "$($ReportOutPath)\$((Get-Date).ToString('yyyy-MM-dd'))-$($ReportName).xlsx" 35 | # WHAT ROW TO START THE DATA ON 36 | $HeaderRow = 7 37 | <# 38 | SCALE THE RENDERED PDF DOWN TO $Zoom 39 | SO IT WILL FIT WITHDH WISE ON THE PAGE 40 | #> 41 | $Zoom = 35 42 | # VALUES: PORTRIAIT = 1, LANDSCAPE = 2 43 | $Orientation = 2 44 | 45 | # LOGO 46 | $LogoPath = "C:\Reports\logo.png" 47 | 48 | # SCALE TO SIZE 49 | $LogoScale = .16 50 | 51 | <# 52 | ENUMERATIONS FOR THE TABLE STYLES CAN BE FOUND HERE: 53 | https://learn.microsoft.com/en-us/javascript/api/excel/excel.builtintablestyle?view=excel-js-preview 54 | 55 | SO SWAP IT OUT FOR ONE YOU LIKE BETTER 56 | #> 57 | $TableName = "Activities" 58 | $TableStyle = "TableStyleMedium9" 59 | 60 | # GET FILES BASED ON FILTERS 61 | $Filters = @( 62 | "objectType eq `"NAS`"", 63 | "and not exists (tags.skippedAcl or tags.skippedData or tags.skippedFiltered)", 64 | "and itemType eq `"file`"" 65 | ) 66 | 67 | function connect-dmapi { 68 | <# 69 | .SYNOPSIS 70 | Connect to the PowerProtect Data Manager REST API. 71 | 72 | .DESCRIPTION 73 | Creates a credentials file for PowerProtect Data Manager if one does not exist. 74 | Connects to the PowerProtect Data Manager REST API 75 | 76 | .PARAMETER Server 77 | Specifies the FQDN of the PowerProtect Data Manager server. 78 | 79 | .OUTPUTS 80 | System.Object 81 | $global:AuthObject 82 | 83 | .EXAMPLE 84 | PS> connect-ppdmapi -Server 'ppdm-01.vcorp.local' 85 | 86 | .LINK 87 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 88 | 89 | #> 90 | [CmdletBinding()] 91 | param ( 92 | [Parameter( Mandatory=$true)] 93 | [string]$Server 94 | ) 95 | begin { 96 | # CHECK TO SEE IF CREDS FILE EXISTS IF NOT CREATE ONE 97 | $Exists = Test-Path -Path ".\$($Server).xml" -PathType Leaf 98 | if($Exists) { 99 | $Credential = Import-CliXml ".\$($Server).xml" 100 | } else { 101 | $Credential = Get-Credential 102 | $Credential | Export-CliXml ".\$($Server).xml" 103 | } 104 | } 105 | process { 106 | $Login = @{ 107 | username="$($Credential.username)" 108 | password="$(ConvertFrom-SecureString -SecureString $Credential.password -AsPlainText)" 109 | } 110 | # LOGON TO THE POWERPROTECT API 111 | $Auth = Invoke-RestMethod -Uri "https://$($Server):$($Port)/api/$($ApiVersion)/login" ` 112 | -Method POST ` 113 | -ContentType 'application/json' ` 114 | -Body (ConvertTo-Json $Login) ` 115 | -SkipCertificateCheck 116 | $Object = @{ 117 | server ="https://$($Server):$($Port)/api/$($ApiVersion)" 118 | token= @{ 119 | authorization="Bearer $($Auth.access_token)" 120 | } #END TOKEN 121 | } #END AUTHOBJ 122 | 123 | $global:AuthObject = $Object 124 | 125 | $global:AuthObject | Format-List 126 | 127 | } #END PROCESS 128 | } #END FUNCTION 129 | 130 | function disconnect-dmapi { 131 | <# 132 | .SYNOPSIS 133 | Disconnect from the PowerProtect Data Manager REST API. 134 | 135 | .DESCRIPTION 136 | Destroys the bearer token contained with $global:AuthObject 137 | 138 | .OUTPUTS 139 | System.Object 140 | $global:AuthObject 141 | 142 | .EXAMPLE 143 | PS> disconnect-dmapi 144 | 145 | .LINK 146 | https://developer.dell.com/apis/4378/versions/19.14.0/docs/getting%20started/authentication-and-authorization.md 147 | 148 | #> 149 | [CmdletBinding()] 150 | param ( 151 | ) 152 | begin {} 153 | process { 154 | #LOGOFF OF THE POWERPROTECT API 155 | Invoke-RestMethod -Uri "$($AuthObject.server)/logout" ` 156 | -Method POST ` 157 | -ContentType 'application/json' ` 158 | -Headers ($AuthObject.token) ` 159 | -SkipCertificateCheck 160 | 161 | $global:AuthObject = $null 162 | } 163 | } #END FUNCTION 164 | 165 | function get-dmprotectionpolicies { 166 | <# 167 | .SYNOPSIS 168 | Get PowerProtect Data Manager protection policies 169 | 170 | .DESCRIPTION 171 | Get PowerProtect Data Manager protection policies based on filters 172 | 173 | .PARAMETER Filters 174 | An array of values used to filter the query 175 | 176 | .PARAMETER PageSize 177 | An int representing the desired number of elements per page 178 | 179 | .OUTPUTS 180 | System.Array 181 | 182 | .EXAMPLE 183 | PS> # Get a protection policy 184 | PS> $Filters = @( 185 | "name eq `"Policy-VM01`"" 186 | ) 187 | PS> $Policy = get-dmprotectionpolicies -Filters $Filters -PageSize 100 188 | 189 | .LINK 190 | https://developer.dell.com/apis/4378/versions/19.14.0/reference/ppdm-public.yaml/paths/~1api~1v2~1protection-policies/get 191 | 192 | #> 193 | [CmdletBinding()] 194 | param ( 195 | [Parameter( Mandatory=$false)] 196 | [array]$Filters, 197 | [Parameter( Mandatory=$true)] 198 | [int]$PageSize 199 | ) 200 | begin {} 201 | process { 202 | 203 | $Page = 1 204 | $Results = @() 205 | $Endpoint = "protection-policies" 206 | 207 | if($Filters.Length -gt 0) { 208 | $Join = ($Filters -join ' ') -replace '\s','%20' -replace '"','%22' 209 | $Endpoint = "$($Endpoint)?filter=$($Join)" 210 | }else { 211 | $Endpoint = "$($Endpoint)?" 212 | } 213 | $Query = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)pageSize=$($PageSize)&page=$($Page)" ` 214 | -Method GET ` 215 | -ContentType 'application/json' ` 216 | -Headers ($AuthObject.token) ` 217 | -SkipCertificateCheck 218 | 219 | # CAPTURE THE RESULTS 220 | $Results = $Query.content 221 | 222 | if($Query.page.totalPages -gt 1) { 223 | # INCREMENT THE PAGE NUMBER 224 | $Page++ 225 | # PAGE THROUGH THE RESULTS 226 | do { 227 | $Paging = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)pageSize=$($PageSize)&page=$($Page)" ` 228 | -Method GET ` 229 | -ContentType 'application/json' ` 230 | -Headers ($AuthObject.token) ` 231 | -SkipCertificateCheck 232 | 233 | # CAPTURE THE RESULTS 234 | $Results += $Paging.content 235 | 236 | # INCREMENT THE PAGE NUMBER 237 | $Page++ 238 | } 239 | until ($Paging.page.number -eq $Query.page.totalPages) 240 | } 241 | return $Results 242 | 243 | } # END PROCESS 244 | } 245 | function get-dmfileinstances { 246 | <# 247 | .SYNOPSIS 248 | Get PowerProtect Data Manager file instances 249 | 250 | .DESCRIPTION 251 | Get PowerProtect Data Manager file instances based on filters 252 | 253 | .PARAMETER Filters 254 | An array of values used to filter the query 255 | 256 | .PARAMETER PageSize 257 | An int representing the desired number of elements per page 258 | 259 | .OUTPUTS 260 | System.Array 261 | 262 | .EXAMPLE 263 | PS> # Get file instances 264 | PS> $Filters = @( 265 | "objectType eq `"NAS`"", 266 | "and not exists (tags.skippedAcl or tags.skippedData or tags.skippedFiltered)", 267 | "and itemType eq `"file`"" 268 | ) 269 | PS> $Query = get-dmfileinstances -Filters $Filters -PageSize $PageSize 270 | 271 | #> 272 | [CmdletBinding()] 273 | param ( 274 | [Parameter( Mandatory=$true)] 275 | [array]$Filters, 276 | [Parameter( Mandatory=$true)] 277 | [int]$PageSize 278 | ) 279 | begin {} 280 | process { 281 | 282 | $Page = 1 283 | $Results = @() 284 | $Endpoint = "file-instances" 285 | 286 | if($Filters.Length -gt 0) { 287 | $Join = ($Filters -join ' ') -replace '\s','%20' -replace '"','%22' 288 | $Endpoint = "$($Endpoint)?filter=$($Join)" 289 | } 290 | 291 | $Query = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&pageSize=$($PageSize)&page=$($Page)" ` 292 | -Method GET ` 293 | -ContentType 'application/json' ` 294 | -Headers ($AuthObject.token) ` 295 | -SkipCertificateCheck 296 | 297 | # CAPTURE THE RESULTS 298 | $Results = $Query.content 299 | 300 | if($Query.page.totalPages -gt 1) { 301 | # INCREMENT THE PAGE NUMBER 302 | $Page++ 303 | # PAGE THROUGH THE RESULTS 304 | do { 305 | $Paging = Invoke-RestMethod -Uri "$($AuthObject.server)/$($Endpoint)&pageSize=$($PageSize)&page=$($Page)" ` 306 | -Method GET ` 307 | -ContentType 'application/json' ` 308 | -Headers ($AuthObject.token) ` 309 | -SkipCertificateCheck 310 | 311 | # CAPTURE THE RESULTS 312 | $Results += $Paging.content 313 | 314 | # INCREMENT THE PAGE NUMBER 315 | $Page++ 316 | } 317 | until ($Paging.page.number -eq $Query.page.totalPages) 318 | } 319 | return $Results 320 | 321 | } # END PROCESS 322 | } 323 | Function Convert-BytesToSize 324 | { 325 | <# 326 | .SYNOPSIS 327 | Converts any integer size given to a user friendly size. 328 | 329 | .DESCRIPTION 330 | Converts any integer size given to a user friendly size. 331 | 332 | .PARAMETER size 333 | Used to convert into a more readable format. 334 | Required Parameter 335 | 336 | .EXAMPLE 337 | Convert-BytesToSize -Size 134217728 338 | Converts size to show 128MB 339 | 340 | .LINK 341 | https://learn-powershell.net/2010/08/29/convert-bytes-to-highest-available-unit/ 342 | #> 343 | 344 | [CmdletBinding()] 345 | Param 346 | ( 347 | [parameter(Mandatory=$false,Position=0)][int64]$Size 348 | 349 | ) 350 | 351 | # DETERMINE SIZE IN BASE2 352 | Switch ($Size) 353 | { 354 | {$Size -gt 1PB} 355 | { 356 | $NewSize = “$([math]::Round(($Size /1PB),1))PB” 357 | Break; 358 | } 359 | {$Size -gt 1TB} 360 | { 361 | $NewSize = “$([math]::Round(($Size /1TB),1))TB” 362 | Break; 363 | } 364 | {$Size -gt 1GB} 365 | { 366 | $NewSize = “$([math]::Round(($Size /1GB),1))GB” 367 | Break; 368 | } 369 | {$Size -gt 1MB} 370 | { 371 | $NewSize = “$([math]::Round(($Size /1MB),1))MB” 372 | Break; 373 | } 374 | {$Size -gt 1KB} 375 | { 376 | $NewSize = “$([math]::Round(($Size /1KB),1))KB” 377 | Break; 378 | } 379 | Default 380 | { 381 | $NewSize = “$([math]::Round($Size,2))Bytes” 382 | Break; 383 | } 384 | } 385 | Return $NewSize 386 | 387 | } 388 | 389 | 390 | # ITERATE OVER THE PPDM HOSTS 391 | $Files = @() 392 | $Servers | ForEach-Object { 393 | foreach($Retry in $Retires) { 394 | try { 395 | # CONNECT THE THE REST API 396 | connect-dmapi -Server $_ 397 | # QUERY FILES 398 | $Query = get-dmfileinstances -Filters $Filters -PageSize $PageSize 399 | 400 | 401 | $Query | ForEach-Object { 402 | $Filters = @("id eq `"$($_.protectionPolicyId)`"") 403 | $Policy = get-dmprotectionpolicies -Filters $Filters -PageSize $PageSize 404 | $Object = [ordered]@{ 405 | id = $_.id 406 | type = $_.type 407 | itemType = $_.itemType 408 | backupType = $_.backupType 409 | name = $_.name 410 | location = $_.location 411 | size = Convert-BytesToSize -Size $_.size 412 | copyStartDate = $_.copyStartDate 413 | copyEndDate = $_.copyEndDate 414 | updatedAt = $_.updatedAt 415 | createdAt = $_.createdAt 416 | protectionPolicyId = $_.protectionPolicyId 417 | protectionPolicyName = $Policy.name 418 | sourceServer = $_.sourceServer 419 | assetName = $_.assetName 420 | assetId = $_.assetId 421 | diskLabel = $_.diskLabel 422 | diskName = $_.diskName 423 | objectType = $_.objectType 424 | } 425 | $Files += (New-Object -TypeName pscustomobject -Property $Object) 426 | } 427 | 428 | # DISCONNECT THE THE REST API 429 | disconnect-dmapi 430 | # BREAK OUT OF THE CURRENT LOOP (RETRIES) 431 | break; 432 | } catch { 433 | if($Retry -lt $Retires.length) { 434 | Write-Host "[WARNING]: $($_). Sleeping $($Seconds) seconds... Attempt #: $($Retry)" -ForegroundColor Yellow 435 | Start-Sleep -Seconds $Seconds 436 | } else { 437 | Write-Host "[ERROR]: $($_). Attempts: $($Retry), moving on..." -ForegroundColor Red 438 | } 439 | } 440 | } # END RETRIES 441 | } 442 | 443 | # LAUNCH EXCEL 444 | $Excel = New-Object -ComObject excel.application 445 | $xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type] 446 | 447 | # SURPRESS THE UI 448 | $Excel.visible = $false 449 | 450 | # CREATE A WORKBOOK 451 | $Workbook = $Excel.Workbooks.Add() 452 | 453 | # GET A HANDLE ON THE FIRST WORKSHEET 454 | $Worksheet = $Workbook.Worksheets.Item(1) 455 | 456 | # ADD A NAME TO THE FIRST WORKSHEET 457 | $Worksheet.Name = "Activities" 458 | 459 | # LOGO PROPERTIES 460 | $Logo = New-Object System.Drawing.Bitmap $LogoPath 461 | 462 | # ADD IMAGE TO THE FIRST WORKSHEET 463 | $Logo = New-Object System.Drawing.Bitmap $LogoPath 464 | $Worksheet.Shapes.AddPicture("$($LogoPath)",1,0,0,0,$Logo.Width*$LogoScale,$Logo.Height*$LogoScale) ` 465 | | Out-Null 466 | 467 | # DEFINE THE HEADER ROW (row, column) 468 | $Excel.cells.item($HeaderRow,1) = "row" 469 | $Excel.cells.item($HeaderRow,2) = "id" 470 | $Excel.cells.item($HeaderRow,3) = "type" 471 | $Excel.cells.item($HeaderRow,4) = "itemType" 472 | $Excel.cells.item($HeaderRow,5) = "backupType" 473 | $Excel.cells.item($HeaderRow,6) = "name" 474 | $Excel.cells.item($HeaderRow,7) = "location" 475 | $Excel.cells.item($HeaderRow,8) = "size" 476 | $Excel.cells.item($HeaderRow,9) = "copyStartDate" 477 | $Excel.cells.item($HeaderRow,10) = "copyEndDate" 478 | $Excel.cells.item($HeaderRow,11) = "updatedAt" 479 | $Excel.cells.item($HeaderRow,12) = "createdAt" 480 | $Excel.cells.item($HeaderRow,13) = "protectionPolicyName" 481 | $Excel.cells.item($HeaderRow,14) = "sourceServer" 482 | $Excel.cells.item($HeaderRow,15) = "assetName" 483 | $Excel.cells.item($HeaderRow,16) = "assetId" 484 | $Excel.cells.item($HeaderRow,17) = "diskLabel" 485 | $Excel.cells.item($HeaderRow,18) = "diskName" 486 | $Excel.cells.item($HeaderRow,19) = "objectType" 487 | 488 | for($i=0;$i -lt $Files.length; $i++) { 489 | 490 | Write-Progress -Activity "Processing records..." ` 491 | -Status "$($i-1) of $($Files.length) - $([math]::round((($i/$Files.length)*100),2))% " ` 492 | -PercentComplete (($i/$Files.length)*100) 493 | 494 | # SET THE ROW OFFSET 495 | $RowOffSet = $HeaderRow+1+$i 496 | $Excel.cells.item($RowOffSet,1) = $i+1 497 | $Excel.cells.item($RowOffSet,2) = $Files[$i].id 498 | $Excel.cells.item($RowOffSet,3) = $Files[$i].type 499 | $Excel.cells.item($RowOffSet,4) = $Files[$i].itemType 500 | $Excel.cells.item($RowOffSet,5) = $Files[$i].backupType 501 | $Excel.cells.item($RowOffSet,6) = $Files[$i].name 502 | $Excel.cells.item($RowOffSet,7) = $Files[$i].location 503 | $Excel.cells.item($RowOffSet,8) = $Files[$i].size 504 | $Excel.cells.item($RowOffSet,9) = $Files[$i].copyStartDate 505 | $Excel.cells.item($RowOffSet,10) = $Files[$i].copyEndDate 506 | $Excel.cells.item($RowOffSet,11) = $Files[$i].updatedAt 507 | $Excel.cells.item($RowOffSet,12) = $Files[$i].createdAt 508 | $Excel.cells.item($RowOffSet,13) = $Files[$i].protectionPolicyName 509 | $Excel.cells.item($RowOffSet,14) = $Files[$i].sourceServer 510 | $Excel.cells.item($RowOffSet,15) = $Files[$i].assetName 511 | $Excel.cells.item($RowOffSet,16) = $Files[$i].assetId 512 | $Excel.cells.item($RowOffSet,17) = $Files[$i].diskLabel 513 | $Excel.cells.item($RowOffSet,18) = $Files[$i].diskName 514 | $Excel.cells.item($RowOffSet,19) = $Files[$i].objectType 515 | 516 | } 517 | 518 | <# 519 | SET CELLS FOR ALL ROWS TO 1.5 TIMES NORAML SIZE 520 | SET CELLS FOR ALL ROWS TO VERTICALLY ALIGN CENTER 521 | SO IT DOESN'T HIDE _ CHARACTERS WHEN EXPORTING TO PDF 522 | #> 523 | $WorksheetRange = $Worksheet.UsedRange 524 | $WorksheetRange.EntireRow.RowHeight = $WorksheetRange.EntireRow.RowHeight * 1.5 525 | $WorksheetRange.EntireRow.VerticalAlignment = ` 526 | [Microsoft.Office.Interop.Excel.XLVAlign]::xlVAlignCenter 527 | 528 | # AUTO SIZE COLUMNS 529 | $WorksheetRange.EntireColumn.AutoFit() | Out-Null 530 | 531 | # CREATE A TABLE IN EXCEL 532 | $TableObject = $Excel.ActiveSheet.ListObjects.Add(` 533 | [Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange,` 534 | $Worksheet.UsedRange,$null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes ` 535 | ) 536 | # TABLE NAME & STYLE 537 | $TableObject.Name = $TableName 538 | $TableObject.TableStyle = $TableStyle 539 | 540 | # EXPORT TO PDF 541 | if($PDF) { 542 | # PDF SETTINGS 543 | $Worksheet.PageSetup.Zoom = $Zoom 544 | $Worksheet.PageSetup.Orientation = $Orientation 545 | $ReportOutFile = "$($ReportOutPath)\$((Get-Date).ToString('yyyy-MM-dd'))-$($ReportName).pdf" 546 | $Worksheet.ExportAsFixedFormat($xlFixedFormat::xlTypePDF,$ReportOutFile) 547 | } else { 548 | $Workbook.SaveAs($ReportOutFile) 549 | } 550 | 551 | # EXIT EXCEL 552 | $Excel.Quit() 553 | [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel) | Out-Null 554 | Stop-Process -Name "EXCEL" --------------------------------------------------------------------------------