update
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
*.swp
|
||||
*.pyc
|
||||
classes/__pycache__
|
||||
log/*
|
||||
@@ -0,0 +1,110 @@
|
||||
NDIT Cohesity Scripts
|
||||
-------------------------
|
||||
These scripts factilitate the automation of NDIT's data protection program with Cohesity. Theses scripts are designed to be used by authorized North Dakota State NDIT personnel only.
|
||||
|
||||
Contents
|
||||
--------
|
||||
* classes/cohesityAPI.py
|
||||
* Defines functions that have been tested and setup in accordance with NDIT Cohesity operating guidelines.
|
||||
|
||||
* classes/serviceNowAPI.py
|
||||
* Defines functions used for opening and assigning tickets for Cohesity issues
|
||||
|
||||
* classes/sharePointAPI.py
|
||||
* Defines functions used for looking up assets in the sharepoint configuration management database (CMDB)
|
||||
|
||||
* cancelJobs.py
|
||||
* Used for mass cancellation of all currently running jobs, typically used for vCenter maintenance.
|
||||
|
||||
* dailyErrors.py
|
||||
* Pulls all protection job runs for the previous 24 hours and creates ServiceNow tickets for any jobs in a Failure state
|
||||
* Sends an email summary to the backup teams for all job states on a daily basis.
|
||||
|
||||
* dailyProtectionReview.py
|
||||
* Retrieves all VM objects from vCenter sources that are registerd to Cohesity and verifies that each object is protected or exempted in accordance with NDIT procedures.
|
||||
* Reviews all protection jobs to ensure empty jobs are paused so they don't generate erroneous errors as well as ensures all popultated jobs are running.
|
||||
* Creates SerivceNow Incidents for:
|
||||
* Unprotected VMs
|
||||
* Unprotected AppNames
|
||||
* Populated Jobs that are paused
|
||||
|
||||
* findDuplicates.py
|
||||
* Pulls all backup jobs per vCenter and determines if a VM is listed in more than one group.
|
||||
|
||||
* pauseOrResumeJob.py
|
||||
* Used to mass pause or resume protectiong jobs, typically used for vCenter maintenance.
|
||||
|
||||
* sqlServerRegistration.py
|
||||
* Determines if the VM that identified by '-s' has been setup as a SQL server source, if not register it as one and add it to the appropriate SQL server protection group provided by CMDB lookup or specified by '-p'.
|
||||
* Pull all kSQL object types within Cohesity and ensure the ITD-COHESITY-DBA group has access to all objects.
|
||||
|
||||
* updateProtectionGroupTags.py
|
||||
* Used to include or exclude vmWare protection job tags across a vCenter or individual job.
|
||||
|
||||
* beta/getSQLSourceErrorMessages.py
|
||||
* Pulls any failed health check information on SQL servers that could prevent the SQL server from getting backed up
|
||||
* Attempts to refresh the source to eliminate false positives
|
||||
* Opens a ServiceNow ticket if refreshing the source does not resolve the issue.
|
||||
|
||||
ToDo
|
||||
----
|
||||
* dailyProtectionReview.py
|
||||
* BUG:
|
||||
|
||||
* RFE:
|
||||
* SeriveNow CMDB lookups will need to be used for AppName and exemnptions, see RFE 001 & RFE 002 sections in the script
|
||||
|
||||
* dailyErrors.py
|
||||
* RFE:
|
||||
* Determine wheter or not we need to open tickets for jobs in a warning state.
|
||||
|
||||
* findDuplicates.py
|
||||
* RFE:
|
||||
* Determine if this script needs to be ran on a routine basis.
|
||||
* Add functionality to open tickets on duplicates
|
||||
|
||||
* updateProtectionGroupTags.py
|
||||
* RFE:
|
||||
* Update API calls to use class calls instead of GetFilteredRequest()
|
||||
* Update tag lookup section to use the function in the class instead of the local function in the script
|
||||
|
||||
* sqlServerRegistration.py
|
||||
* RFE:
|
||||
* SeriveNow CMDB lookups will need to be used for AppName and exemnptions, see RFE 001 & RFE 002 sections in the script
|
||||
* Update API calls to use class calls instead of GetFilteredRequest()
|
||||
|
||||
Example Python Script
|
||||
---------------------
|
||||
```
|
||||
import json
|
||||
import sys
|
||||
import certifi
|
||||
import urllib3
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
try:
|
||||
|
||||
# Define create and instantiate the API for a Cohesity cluster
|
||||
mdn = cohesity.API('itdmdndpc01.nd.gov')
|
||||
|
||||
# Get the Auth Token and add it to the API header calls
|
||||
mdnToken = mdn.GetAuthToken()
|
||||
mdn.UpdateHeaders(mdnToken['accessToken'])
|
||||
|
||||
# Get a list of objects from an API call, for a full listing of API calls, see developer.cohesity.com
|
||||
vms = mdn.GetFilteredRequest("/public/protectionSources/virtualMachines", "?protected=true")
|
||||
|
||||
# Print out an indented copy of the JSON data; use jsonparser.org or notepad++ to make it easier to read
|
||||
print(json.dumps(vms, indent=4))
|
||||
|
||||
# Do something with the data based on the JSON output
|
||||
for vm in vms:
|
||||
print(vm['name'])
|
||||
|
||||
except OSError as cohesityError:
|
||||
print('Cohesity Error: ' + cohesityError)
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
# Add ExcludeVMTagIds
|
||||
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
|
||||
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
|
||||
|
||||
ForEach ($ProtectionGroup in $AllProtectionGroups) {
|
||||
Write-Warning ("Start " + $ProtectionGroup.name)
|
||||
# Tag to Add to Exclude
|
||||
$SourceObjectsToAddToExclude = $AllProtectionSourceObject | where-object { $_.Name -eq "Test" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
|
||||
|
||||
If ($ProtectionGroup.excludeVmTagIds) {
|
||||
# if an exclusion already exists
|
||||
$ProtectionGroup.excludeVmTagIds += , @($SourceObjectsToAddToExclude.Id)
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
} Else {
|
||||
# if an exclusion does not already exist
|
||||
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name excludeVmTagIds -Value @(, @($SourceObjectsToAddToExclude.Id))
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Remove ExcludeVMTagIds
|
||||
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
|
||||
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
|
||||
|
||||
ForEach ($ProtectionGroup in $AllProtectionGroups) {
|
||||
Write-Warning ("Start " + $ProtectionGroup.name)
|
||||
# Tag to Remove to Exclude
|
||||
$SourceObjectsToRemoveFromExclude = $AllProtectionSourceObject | Where-Object { $_.Name -eq "Test" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
|
||||
|
||||
if (! $ProtectionGroup.PSObject.Properties['excludeVmTagIds']) {
|
||||
|
||||
}
|
||||
else {
|
||||
$NewArray = @()
|
||||
ForEach ($item in $ProtectionGroup.PSObject.Properties['excludeVmTagIds'].value) {
|
||||
if ($item -ne $SourceObjectsToRemoveFromExclude.id) { $NewArray += $item }
|
||||
}
|
||||
|
||||
If(@($NewArray).count -gt 0){
|
||||
$ProtectionGroup.excludeVmTagIds = ,@($NewArray)
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
}
|
||||
If(@($NewArray).count -eq 0){
|
||||
$ProtectionGroup.PSObject.Properties.remove('excludeVmTagIds')
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Add VMTagIds
|
||||
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
|
||||
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
|
||||
|
||||
ForEach ($ProtectionGroup in $AllProtectionGroups) {
|
||||
Write-Warning ("Start " + $ProtectionGroup.name)
|
||||
# Tag to Add to Include
|
||||
$SourceObjectsToAddToInclude = $AllProtectionSourceObject | where-object { $_.Name -eq "ITD-POC-Video" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
|
||||
|
||||
If ($ProtectionGroup.VmTagIds) {
|
||||
# if an exclusion already exists
|
||||
$ProtectionGroup.VmTagIds += , @($SourceObjectsToAddToInclude.Id)
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
} Else {
|
||||
# if an exclusion does not already exist
|
||||
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name VmTagIds -Value @(, @($SourceObjectsToAddToInclude.Id))
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Remove VMTagIds
|
||||
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
|
||||
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
|
||||
|
||||
ForEach ($ProtectionGroup in $AllProtectionGroups) {
|
||||
Write-Warning ("Start " + $ProtectionGroup.name)
|
||||
# Tag to Remove from Include
|
||||
$SourceObjectsToRemoveFromInclude = $AllProtectionSourceObject | where-object { $_.Name -eq "ITD-POC-Video" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
|
||||
|
||||
If ($ProtectionGroup.VmTagIds) {
|
||||
# if an exclusion already exists
|
||||
$ProtectionGroup.VmTagIds += , @($SourceObjectsToRemoveFromInclude.Id)
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
} Else {
|
||||
# if an exclusion does not already exist
|
||||
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name VmTagIds -Value @(, @($SourceObjectsToRemoveFromInclude.Id))
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>ITD.Cohesity</id>
|
||||
<version>$VERSIONHERE$</version>
|
||||
<authors>Cliff Cogdill / Zack Meier</authors>
|
||||
<description>Functions for Cohesity administration</description>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -0,0 +1,10 @@
|
||||
@{
|
||||
RootModule = 'ITD.Cohesity.psm1'
|
||||
ModuleVersion = '<ModuleVersion>'
|
||||
GUID = '9b7f0813-ae13-4d3b-9b5a-608374755382'
|
||||
Author = 'Cliff Cogdill / Zack Meier '
|
||||
CompanyName = 'State of North Dakota'
|
||||
PowerShellVersion = '7'
|
||||
CompatiblePSEditions = 'Desktop','Core'
|
||||
FunctionsToExport = @(<FunctionsToExport>)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run a single on-demand VMware incremental backup
|
||||
.DESCRIPTION
|
||||
Run a single on-demand VMware incremental backup
|
||||
.EXAMPLE
|
||||
Start-ITDCohesityOnDemandVMIncremental -ComputerName itdxyz.nd.gov
|
||||
.NOTES
|
||||
General notes
|
||||
.COMPONENT
|
||||
Cohesity
|
||||
.ROLE
|
||||
The role this cmdlet belongs to
|
||||
.FUNCTIONALITY
|
||||
The functionality that best describes this cmdlet
|
||||
#>
|
||||
function Start-ITDCohesityOnDemandVMIncremental {
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
|
||||
[Parameter()]
|
||||
$ComputerName
|
||||
)
|
||||
|
||||
begin {
|
||||
}
|
||||
|
||||
process {
|
||||
$CohesityVMwareVM = Get-CohesityProtectionSourceObject | Where-Object Name -eq $ComputerName #| Select-Object -Unique Id, Name, Environment, ParentId
|
||||
Start-CohesityProtectionJob -Id (Get-CohesityProtectionJob | Where-Object sourceIds -Match $CohesityVmwareVM.parentId) -SourceIds $CohesityVMwareVM.id
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Create a Protection Group based on VMware AppName Tag, one protection group for each VMware source.
|
||||
.DESCRIPTION
|
||||
Long description
|
||||
.EXAMPLE
|
||||
New-ITDCohesityProtectionGroup -AppName ITD-POC-zmeier
|
||||
.NOTES
|
||||
General notes
|
||||
.COMPONENT
|
||||
The component this cmdlet belongs to
|
||||
.ROLE
|
||||
The role this cmdlet belongs to
|
||||
.FUNCTIONALITY
|
||||
The functionality that best describes this cmdlet
|
||||
#>
|
||||
function New-ITDCohesityProtectionGroupWIP {
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
[Parameter()]
|
||||
[string[]]
|
||||
$AppName,
|
||||
|
||||
[switch]
|
||||
$NewBuildsOnly
|
||||
)
|
||||
|
||||
begin {
|
||||
}
|
||||
|
||||
process {
|
||||
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
|
||||
# create protection group based on appname, exclude placeholders
|
||||
ForEach ($app in $AppName) {
|
||||
$ProtectionJob = $null
|
||||
|
||||
$SourceObjects = $AllProtectionSourceObject | Where-Object Name -eq $app
|
||||
$StartHour = Get-Random -Minimum 18 -Maximum 23
|
||||
$StartDateTime = ([DateTime]::Today.AddHours($StartHour)).AddMinutes(30)
|
||||
ForEach ($SourceObject in $SourceObjects) {
|
||||
$Source = Get-CohesityProtectionSourceObject -Id $SourceObject.parentId
|
||||
switch ($Source.environment) {
|
||||
'kVMware' {
|
||||
$ProtectionJobName = ($app + "@" + $Source.Name.split('.')[0])
|
||||
}
|
||||
'kPhysical' {
|
||||
$ProtectionJobName = ($app + "@" + "physical")
|
||||
}
|
||||
}
|
||||
|
||||
$SRMTagPlaceholderObject = $AllProtectionSourceObject | Where-Object {$_.Name -eq "Placeholder" -and $_.ParentId -eq $SourceObject.parentId}
|
||||
|
||||
New-CohesityProtectionJob -Name $ProtectionJobName `
|
||||
-PolicyName "ITD-Bronze" `
|
||||
-VmTagIds $SourceObject.Id `
|
||||
-ParentSourceId $SourceObject.parentId `
|
||||
-ExcludeVmTagIds $SRMTagPlaceholderObject.id `
|
||||
-Environment $Source.environment `
|
||||
-Timezone "America/Chicago" `
|
||||
-ScheduleStartTime $StartDateTime `
|
||||
-StorageDomainName DefaultStorageDomain `
|
||||
|
||||
$indexingPolicy = [PSCustomObject]@{
|
||||
disableIndexing = $false;
|
||||
allowPrefixes = @('/');
|
||||
denyPrefixes = @('/$Recycle.Bin', '/Windows', '/ProgramData', '/System Volume Information', '/Users/*/AppData', '/Recovery', '/usr', '/sys', '/proc', '/lib', '/grub', '/grub2', 'opt/splunk', '/splunk')
|
||||
}
|
||||
|
||||
$ProtectionJob = Get-CohesityProtectionJob -Names $ProtectionJobName
|
||||
$ProtectionJob | Add-Member -MemberType NoteProperty -Name indexingPolicy -Value $indexingPolicy
|
||||
Set-CohesityProtectionJob -ProtectionJob $ProtectionJob -Confirm:$false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Add backup object to Cohesity User and Groups
|
||||
This script lists users and groups and writes the output to a text file (addObjectToUserAccessList-clusterName.txt)
|
||||
|
||||
# Components
|
||||
- addObjectToUserAccessList.ps1: the main powershell script
|
||||
- cohesity-api.ps1: the Cohesity REST API helper module
|
||||
|
||||
Place the files in a folder together and run the main script like so:
|
||||
|
||||
# Command line example
|
||||
```
|
||||
./addObjectToUserAccessList.ps1 -vip mycluster `
|
||||
-username myuser `
|
||||
-domain mydomain.net `
|
||||
-principal mydomain.net/myuser `
|
||||
-addObject vm1, server1.mydomain.net `
|
||||
-removeObject vm2, vm3 `
|
||||
-addView view1, view2 `
|
||||
-removeView view3, view4
|
||||
```
|
||||
|
||||
# Parameters
|
||||
|
||||
- vip: Cohesity Cluster to connect to
|
||||
- username: Cohesity username
|
||||
- domain: (optional) Cohesity logon domain (defaults to local)
|
||||
- principal: name(s) (comma separated) of user or group to modify (e.g. mydomain.net/myuser, or mylocaluser)
|
||||
- addObject: (optional) names of registered objects to add to access list (comma separated)
|
||||
- removeObject: (optional) names of registered objects to remove from access list (comma separated)
|
||||
- addView: (optional) names of views to add to access list (comma separated)
|
||||
- removeView: (optional) names of views to remove from access list (comma separated)
|
||||
@@ -0,0 +1,27 @@
|
||||
#Add to Local Security Policy
|
||||
function Add-ServiceLogonRight([string] $Username) {
|
||||
Write-Host "Enable ServiceLogonRight for $Username"
|
||||
|
||||
$tmp = New-TemporaryFile
|
||||
secedit /export /cfg "$tmp.inf" | Out-Null
|
||||
(Get-Content -Encoding ascii "$tmp.inf") -replace '^SeServiceLogonRight .+', "`$0,$Username" | sc -Encoding ascii "$tmp.inf"
|
||||
secedit /import /cfg "$tmp.inf" /db "$tmp.sdb" | Out-Null
|
||||
secedit /configure /db "$tmp.sdb" /cfg "$tmp.inf" | Out-Null
|
||||
Remove-Item $tmp* -ea 0
|
||||
}
|
||||
|
||||
Add-ServiceLogonRight -Username svccohesityadm
|
||||
|
||||
|
||||
sc.exe config "CohesityAgent" obj="NDGOV\svccohesityadm" password="xxxxxxxxx"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#Prompt for secure password & convert to plaintext for DataStage use
|
||||
$dsSecurePass = Read-Host -AsSecureString "Enter $($dsUser) password"
|
||||
$dsBinaryPass = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($dsSecurePass)
|
||||
$dsPass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($dsBinaryPass)
|
||||
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($dsBinaryPass)
|
||||
@@ -0,0 +1,120 @@
|
||||
### process commandline arguments
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $True)][string]$vip, #the cluster to connect to (DNS name or IP)
|
||||
[Parameter(Mandatory = $True)][string]$username, #username (local or AD)
|
||||
[Parameter()][string]$domain = 'local', #local or AD domain
|
||||
[Parameter()][array]$principal,
|
||||
[Parameter()][array]$addObject,
|
||||
[Parameter()][array]$addView,
|
||||
[Parameter()][array]$removeObject,
|
||||
[Parameter()][array]$removeView
|
||||
)
|
||||
|
||||
### source the cohesity-api helper code
|
||||
. $(Join-Path -Path $PSScriptRoot -ChildPath cohesity-api.ps1)
|
||||
|
||||
### authenticate
|
||||
apiauth -vip $vip -username $username -domain $domain
|
||||
|
||||
$users = api get users?_includeTenantInfo=true
|
||||
$groups = api get groups?_includeTenantInfo=true
|
||||
$sources = api get "protectionSources"
|
||||
$views = api get views
|
||||
|
||||
function getObjectId($objectName){
|
||||
$global:_object_id = $null
|
||||
|
||||
function get_nodes($obj){
|
||||
if($obj.protectionSource.name -eq $objectName){
|
||||
$global:_object_id = $obj.protectionSource.id
|
||||
break
|
||||
}
|
||||
if($obj.name -eq $objectName){
|
||||
$global:_object_id = $obj.id
|
||||
break
|
||||
}
|
||||
if($obj.PSObject.Properties['nodes']){
|
||||
foreach($node in $obj.nodes){
|
||||
if($null -eq $global:_object_id){
|
||||
get_nodes $node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($source in $sources){
|
||||
if($null -eq $global:_object_id){
|
||||
get_nodes $source
|
||||
}
|
||||
}
|
||||
return $global:_object_id
|
||||
}
|
||||
|
||||
|
||||
foreach($p in $principal){
|
||||
if($p -match '/'){
|
||||
$d, $p = $p.split('/')
|
||||
}else{
|
||||
$d = 'local'
|
||||
}
|
||||
$ptype = 'user'
|
||||
$thisPrincipal = $users | Where-Object {$_.username -eq $p -and $_.domain -eq $d}
|
||||
if(!$thisPrincipal){
|
||||
$ptype = 'group'
|
||||
$thisPrincipal = $groups | Where-Object {$_.name -eq $p -and $_.domain -eq $d}
|
||||
}
|
||||
if(!$thisPrincipal){
|
||||
Write-Host "Principal $d/$p not found!" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
$access = api get principals/protectionSources?sids=$($thisPrincipal.sid)
|
||||
$newAccess = @{
|
||||
"sourcesForPrincipals" = @(
|
||||
@{
|
||||
"sid" = $thisPrincipal.sid;
|
||||
"protectionSourceIds" = [array]$access.protectionSources.id;
|
||||
"viewNames" = [array]$access.views.name
|
||||
}
|
||||
)
|
||||
}
|
||||
foreach($objectName in $addObject){
|
||||
$objectId = getObjectId $objectName
|
||||
if(!$objectId){
|
||||
Write-Host "Object $objectName not found!" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
"Adding $objectName"
|
||||
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds + $objectId)
|
||||
}
|
||||
foreach($objectName in $removeObject){
|
||||
$objectId = getObjectId $objectName
|
||||
if($objectId){
|
||||
"Removing $objectName"
|
||||
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds | Where-Object {$_ -ne $objectId})
|
||||
}
|
||||
}
|
||||
foreach($viewName in $addView){
|
||||
$view = $views.views | Where-Object {$_.name -eq $viewName}
|
||||
if(!$view){
|
||||
Write-Host "View $viewName not found" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
"Adding $viewName"
|
||||
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames + $view.name)
|
||||
}
|
||||
foreach($viewName in $removeView){
|
||||
"Removing $viewName"
|
||||
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames | Where-Object {$_ -ne $viewName})
|
||||
}
|
||||
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds | Sort-Object -Unique)
|
||||
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames | Sort-Object -Unique)
|
||||
$thisPrincipal.restricted = $True
|
||||
if($ptype -eq 'user'){
|
||||
$null = api put users $thisPrincipal
|
||||
}else{
|
||||
$null = api put groups $thisPrincipal
|
||||
}
|
||||
$null = api put principals/protectionSources $newAccess
|
||||
}
|
||||
|
||||
@@ -0,0 +1,813 @@
|
||||
# . . . . . . . . . . . . . . . . . . .
|
||||
# PowerShell Module for Cohesity API
|
||||
# Version 2020.12.22 - Brian Seltzer
|
||||
# . . . . . . . . . . . . . . . . . . .
|
||||
#
|
||||
# 2020.10.16 - added password parameter to storePasswordInFile function
|
||||
# 2020.10.20 - code cleanup (moved old version history to end of file)
|
||||
# 2020.12.22 - added v2 support for file download
|
||||
#
|
||||
# . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
$versionCohesityAPI = '2020.12.22'
|
||||
|
||||
# demand modern powershell version (must support TLSv1.2)
|
||||
if($Host.Version.Major -le 5 -and $Host.Version.Minor -lt 1){
|
||||
Write-Warning "PowerShell version must be upgraded to 5.1 or higher to connect to Cohesity!"
|
||||
Pause
|
||||
exit
|
||||
}
|
||||
|
||||
$REPORTAPIERRORS = $true
|
||||
|
||||
$pwfile = $(Join-Path -Path $PSScriptRoot -ChildPath YWRtaW4)
|
||||
$apilogfile = $(Join-Path -Path $PSScriptRoot -ChildPath cohesity-api-debug.log)
|
||||
|
||||
# platform detection ==========================================================================
|
||||
if ($PSVersionTable.Platform -eq 'Unix') {
|
||||
$CONFDIR = '~/.cohesity-api'
|
||||
if ($(Test-Path $CONFDIR) -eq $false) { $null = New-Item -Type Directory -Path $CONFDIR}
|
||||
}else{
|
||||
$registryPath = 'HKCU:\Software\Cohesity-API'
|
||||
$WEBCLI = New-Object System.Net.WebClient;
|
||||
}
|
||||
|
||||
if($PSVersionTable.PSEdition -eq 'Desktop'){
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { return $true }
|
||||
$ignoreCerts = @"
|
||||
public class SSLHandler
|
||||
{
|
||||
public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler()
|
||||
{
|
||||
return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
if(!("SSLHandler" -as [type])){
|
||||
Add-Type -TypeDefinition $ignoreCerts
|
||||
}
|
||||
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler()
|
||||
}
|
||||
|
||||
function __writeLog($logmessage){
|
||||
"$(Get-Date): $logmessage" | Out-File -FilePath $apilogfile -Append
|
||||
}
|
||||
|
||||
# authentication functions ========================================================================
|
||||
|
||||
function apiauth($vip, $username='helios', $domain='local', $passwd=$null, $password = $null, $tenantId = $null, [switch] $quiet, [switch] $noprompt, [switch] $updatePassword, [switch] $helios, [switch] $useApiKey){
|
||||
|
||||
if(-not $vip){
|
||||
if($helios){
|
||||
$vip = 'helios.cohesity.com'
|
||||
}else{
|
||||
Write-Host 'vip is required' -foregroundcolor Yellow
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# parse domain\username or username@domain
|
||||
if($username.Contains('\')){
|
||||
$domain, $username = $username.Split('\')
|
||||
}
|
||||
if($password){ $passwd = $password }
|
||||
if($updatePassword){
|
||||
$fpasswd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
|
||||
if($fpasswd){
|
||||
storePasswordInFile -vip $vip -username $username -domain $domain
|
||||
}else{
|
||||
Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
|
||||
}
|
||||
}
|
||||
# get password
|
||||
if(!$passwd){
|
||||
$passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
|
||||
if(!$passwd -and !$noprompt){
|
||||
Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
|
||||
$passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
|
||||
}
|
||||
if(!$passwd){
|
||||
Write-Host "No password provided for $username at $vip" -ForegroundColor Yellow
|
||||
$global:AUTHORIZED = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$body = ConvertTo-Json @{
|
||||
'domain' = $domain;
|
||||
'username' = $username;
|
||||
'password' = $passwd
|
||||
}
|
||||
|
||||
$global:APIROOT = 'https://' + $vip + '/irisservices/api/v1'
|
||||
$global:APIROOTv2 = 'https://' + $vip + '/v2/'
|
||||
$HEADER = @{'accept' = 'application/json'; 'content-type' = 'application/json'}
|
||||
if($useApiKey){
|
||||
$HEADER['apiKey'] = $passwd
|
||||
$global:HEADER = $HEADER
|
||||
$global:AUTHORIZED = $true
|
||||
$global:CLUSTERSELECTED = $true
|
||||
$cluster = api get cluster
|
||||
if($cluster){
|
||||
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
|
||||
}else{
|
||||
$global:AUTHORIZED = $false
|
||||
}
|
||||
}elseif($vip -eq 'helios.cohesity.com' -or $helios){
|
||||
# Authenticate Helios
|
||||
$HEADER['apiKey'] = $passwd
|
||||
$URL = 'https://helios.cohesity.com/mcm/clusters/connectionStatus'
|
||||
try{
|
||||
if($PSVersionTable.Edition -eq 'Core'){
|
||||
$global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER -SkipCertificateCheck
|
||||
}else{
|
||||
$global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER
|
||||
}
|
||||
$global:HELIOSCONNECTEDCLUSTERS = $global:HELIOSALLCLUSTERS | Where-Object connectedToCluster -eq $true
|
||||
$global:HEADER = $HEADER
|
||||
$global:AUTHORIZED = $true
|
||||
$global:CLUSTERSELECTED = $false
|
||||
$global:CLUSTERREADONLY = $false
|
||||
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
|
||||
}catch{
|
||||
$global:AUTHORIZED = $false
|
||||
__writeLog $_.ToString()
|
||||
if($_.ToString().contains('"message":')){
|
||||
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
|
||||
}else{
|
||||
Write-Host $_.ToString() -foregroundcolor yellow
|
||||
}
|
||||
}
|
||||
}else{
|
||||
# Authenticate Cluster
|
||||
$url = $APIROOT + '/public/accessTokens'
|
||||
try {
|
||||
# authenticate
|
||||
if($PSVersionTable.PSEdition -eq 'Core'){
|
||||
$auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body -SkipCertificateCheck
|
||||
}else{
|
||||
$auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body
|
||||
}
|
||||
# set file transfer details
|
||||
if($PSVersionTable.Platform -eq 'Unix'){
|
||||
$global:CURLHEADER = "authorization: $($auth.tokenType) $($auth.accessToken)"
|
||||
}else{
|
||||
$WEBCLI.Headers['authorization'] = $auth.tokenType + ' ' + $auth.accessToken;
|
||||
}
|
||||
# store token
|
||||
$global:AUTHORIZED = $true
|
||||
$global:CLUSTERSELECTED = $true
|
||||
$global:CLUSTERREADONLY = $false
|
||||
$global:HEADER = @{'accept' = 'application/json';
|
||||
'content-type' = 'application/json';
|
||||
'authorization' = $auth.tokenType + ' ' + $auth.accessToken
|
||||
}
|
||||
if($tenantId){
|
||||
$global:HEADER['x-impersonate-tenant-id'] = "$tenantId/"
|
||||
}
|
||||
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
|
||||
}catch{
|
||||
$global:AUTHORIZED = $false
|
||||
__writeLog $_.ToString()
|
||||
$global:AUTHORIZED = $false
|
||||
if($REPORTAPIERRORS){
|
||||
if($_.ToString().contains('"message":')){
|
||||
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
|
||||
}else{
|
||||
Write-Host $_.ToString() -foregroundcolor yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# select helios access cluster
|
||||
function heliosCluster($clusterName, [switch] $verbose){
|
||||
if($clusterName -and $HELIOSCONNECTEDCLUSTERS){
|
||||
if(! ($clusterName -is [string])){
|
||||
$clusterName = $clusterName.name
|
||||
}
|
||||
$cluster = $HELIOSCONNECTEDCLUSTERS | Where-Object name -eq $clusterName
|
||||
if($cluster){
|
||||
$global:HEADER.accessClusterId = $cluster.clusterId
|
||||
$global:CLUSTERSELECTED = $true
|
||||
$global:CLUSTERREADONLY = (api get /mcm/config).mcmReadOnly
|
||||
if($verbose){
|
||||
Write-Host "Connected ($($cluster.name))" -ForegroundColor Green
|
||||
}
|
||||
}else{
|
||||
Write-Host "Cluster $clusterName not connected to Helios" -ForegroundColor Yellow
|
||||
$global:CLUSTERSELECTED = $false
|
||||
return $null
|
||||
}
|
||||
}else{
|
||||
$HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name | Select-Object -Property name, clusterId, softwareVersion
|
||||
"`ntype heliosCluster <clustername> to connect to a cluster"
|
||||
}
|
||||
if (-not $global:AUTHORIZED){
|
||||
if($REPORTAPIERRORS){
|
||||
Write-Host 'Please use apiauth to connect to helios' -foregroundcolor yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function heliosClusters(){
|
||||
return $HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name
|
||||
}
|
||||
|
||||
# terminate authentication
|
||||
function apidrop([switch] $quiet){
|
||||
$global:AUTHORIZED = $false
|
||||
$global:HEADER = ''
|
||||
$global:HELIOSALLCLUSTERS = $null
|
||||
$global:HELIOSCONNECTEDCLUSTERS = $null
|
||||
if(!$quiet){ Write-Host "Disonnected!" -foregroundcolor green }
|
||||
}
|
||||
|
||||
# api call functions ==============================================================================
|
||||
|
||||
$methods = 'get', 'post', 'put', 'delete'
|
||||
function api($method, $uri, $data, $version=1, [switch]$v2){
|
||||
if (-not $global:AUTHORIZED){
|
||||
if($REPORTAPIERRORS){
|
||||
Write-Host 'Not authenticated to a cohesity cluster' -foregroundcolor yellow
|
||||
if($MyInvocation.PSCommandPath){
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if($method -ne 'get' -and $global:CLUSTERREADONLY -eq $true){
|
||||
Write-Host "Cluster connection is READ-ONLY" -ForegroundColor Yellow
|
||||
break
|
||||
}
|
||||
if (-not $methods.Contains($method)){
|
||||
if($REPORTAPIERRORS){
|
||||
Write-Host "invalid api method: $method" -foregroundcolor yellow
|
||||
}
|
||||
break
|
||||
}
|
||||
try {
|
||||
|
||||
if($version -eq 2 -or $v2){
|
||||
$url = $APIROOTv2 + $uri
|
||||
}else{
|
||||
if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
|
||||
$url = $APIROOT + $uri
|
||||
}
|
||||
$body = ConvertTo-Json -Depth 100 $data
|
||||
if ($PSVersionTable.PSEdition -eq 'Core'){
|
||||
if($body){
|
||||
$result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER -SkipCertificateCheck
|
||||
}else{
|
||||
$result = Invoke-RestMethod -Method $method -Uri $url -Header $HEADER -SkipCertificateCheck
|
||||
}
|
||||
}else{
|
||||
$result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER
|
||||
}
|
||||
return $result
|
||||
}catch{
|
||||
__writeLog $_.ToString()
|
||||
if($REPORTAPIERRORS){
|
||||
if($_.ToString().contains('"message":')){
|
||||
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
|
||||
}else{
|
||||
Write-Host $_.ToString() -foregroundcolor yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# file download function
|
||||
function fileDownload($uri, $fileName, $version=1, [switch]$v2){
|
||||
if (-not $global:AUTHORIZED){ Write-Host 'Please use apiauth to connect to a cohesity cluster' -foregroundcolor yellow; break }
|
||||
try {
|
||||
if($version -eq 2 -or $v2){
|
||||
$url = $APIROOTv2 + $uri
|
||||
}else{
|
||||
if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
|
||||
$url = $APIROOT + $uri
|
||||
}
|
||||
if ($PSVersionTable.Platform -eq 'Unix'){
|
||||
curl -k -s -H "$global:CURLHEADER" -o "$fileName" "$url"
|
||||
}else{
|
||||
if($fileName -notmatch '\\'){
|
||||
$fileName = $(Join-Path -Path $PSScriptRoot -ChildPath $fileName)
|
||||
}
|
||||
$WEBCLI.DownloadFile($url, $fileName)
|
||||
}
|
||||
}catch{
|
||||
__writeLog $_.ToString()
|
||||
$_.ToString()
|
||||
if($_.ToString().contains('"message":')){
|
||||
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
|
||||
}else{
|
||||
Write-Host $_.ToString() -foregroundcolor yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# date functions ==================================================================================
|
||||
|
||||
function timeAgo([int64] $age, [string] $units){
|
||||
$currentTime = [int64](((get-date).ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
|
||||
$secs=@{'seconds'= 1; 'sec'= 1; 'secs' = 1;
|
||||
'minutes' = 60; 'min' = 60; 'mins' = 60;
|
||||
'hours' = 3600; 'hour' = 3600;
|
||||
'days' = 86400; 'day' = 86400;
|
||||
'weeks' = 604800; 'week' = 604800;
|
||||
'months' = 2628000; 'month' = 2628000;
|
||||
'years' = 31536000; 'year' = 31536000 }
|
||||
$age = $age * $secs[$units.ToLower()] * 1000000
|
||||
return [int64] ($currentTime - $age)
|
||||
}
|
||||
|
||||
function usecsToDate($usecs){
|
||||
$unixTime=$usecs/1000000
|
||||
$origin = ([datetime]'1970-01-01 00:00:00')
|
||||
return $origin.AddSeconds($unixTime).ToLocalTime()
|
||||
}
|
||||
|
||||
function dateToUsecs($datestring){
|
||||
if($datestring -isnot [datetime]){ $datestring = [datetime] $datestring }
|
||||
$usecs = [int64](($datestring.ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
|
||||
$usecs
|
||||
}
|
||||
|
||||
# password functions ==============================================================================
|
||||
|
||||
function Get-CohesityAPIPassword($vip, $username, $domain='local'){
|
||||
# parse domain\username or username@domain
|
||||
if($username.Contains('\')){
|
||||
$domain, $username = $username.Split('\')
|
||||
}
|
||||
if($username.Contains('@')){
|
||||
$username, $domain = $username.Split('@')
|
||||
}
|
||||
$passwd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
|
||||
if($passwd){
|
||||
return $passwd
|
||||
}
|
||||
$keyName = "$vip`:$domain`:$username"
|
||||
if($PSVersionTable.Platform -eq 'Unix'){
|
||||
# Unix
|
||||
$keyFile = "$CONFDIR/$keyName"
|
||||
if (Test-Path $keyFile) {
|
||||
$key, $storedPassword = Get-Content $keyFile
|
||||
return Unprotect-CohesityAPIPassword $key $storedPassword
|
||||
}
|
||||
}else{
|
||||
# Windows
|
||||
$storedPassword = Get-ItemProperty -Path "$registryPath" -Name "$keyName" -ErrorAction SilentlyContinue
|
||||
If (($null -ne $storedPassword) -and ($storedPassword.Length -ne 0)) {
|
||||
$securePassword = $storedPassword.$keyName | ConvertTo-SecureString
|
||||
return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $securePassword ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-CohesityAPIPasswordFromFile($vip, $username, $domain){
|
||||
$pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
|
||||
foreach($pwitem in $pwlist){
|
||||
$v, $d, $u, $cpwd = $pwitem.split(":", 4)
|
||||
if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
|
||||
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($cpwd))
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
|
||||
function storePasswordInFile($vip='helios.cohesity.com', $username='helios', $domain='local', [switch]$helios, $password=$null){
|
||||
# parse domain\username or username@domain
|
||||
if($username.Contains('\')){
|
||||
$domain, $username = $username.Split('\')
|
||||
}
|
||||
if($username.Contains('@')){
|
||||
$username, $domain = $username.Split('@')
|
||||
}
|
||||
|
||||
if($vip -eq 'helios.cohesity.com' -and $username -eq 'helios' -and ! $helios){
|
||||
# prompt for vip
|
||||
__writeLog "Prompting for VIP, USERNAME, DOMAIN"
|
||||
$newVip = Read-Host -Prompt "Enter VIP ($vip)"
|
||||
if($newVip -ne ''){ $vip = $newVip }
|
||||
|
||||
# prompt for domain
|
||||
$newDomain = Read-Host -Prompt "Enter domain ($domain)"
|
||||
if($newDomain -ne ''){ $domain = $newDomain }
|
||||
|
||||
# prompt for username
|
||||
$newUsername = Read-Host -Prompt "Enter username ($username)"
|
||||
if($newUsername -ne ''){ $username = $newUsername }
|
||||
}
|
||||
|
||||
# prompt for password
|
||||
__writeLog "Prompting for Password"
|
||||
if(!$password){
|
||||
$secureString = Read-Host -Prompt "Enter password for $domain\$username at $vip" -AsSecureString
|
||||
$passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))
|
||||
}else{
|
||||
$passwd = $password
|
||||
}
|
||||
$opwd = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($passwd))
|
||||
|
||||
$pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
|
||||
$updatedContent = ''
|
||||
$foundPwd = $false
|
||||
foreach($pwitem in $pwlist){
|
||||
$v, $d, $u, $cpwd = $pwitem.split(":", 4)
|
||||
# update existing
|
||||
if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
|
||||
$foundPwd = $true
|
||||
$updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
|
||||
# other existing records
|
||||
}else{
|
||||
if($pwitem -ne ''){
|
||||
$updatedContent += "{0}`n" -f $pwitem
|
||||
}
|
||||
}
|
||||
}
|
||||
# add new
|
||||
if(!$foundPwd){
|
||||
$updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
|
||||
}
|
||||
|
||||
$updatedContent | out-file -FilePath $pwfile
|
||||
Write-Host "Password stored!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
|
||||
function Set-CohesityAPIPassword($vip, $username, $domain='local', $passwd=$null){
|
||||
# prompt for vip
|
||||
if(-not $vip){
|
||||
__writeLog "Prompting for VIP"
|
||||
Write-Host 'VIP: ' -foregroundcolor green -nonewline
|
||||
$vip = Read-Host
|
||||
if(-not $vip){Write-Host 'vip is required' -foregroundcolor red; break}
|
||||
}
|
||||
# prompt for username
|
||||
if(-not $username){
|
||||
__writeLog "Prompting for Username"
|
||||
Write-Host 'Username: ' -foregroundcolor green -nonewline
|
||||
$username = Read-Host
|
||||
if(-not $username){Write-Host 'username is required' -foregroundcolor red; break}
|
||||
}
|
||||
# parse domain\username or username@domain
|
||||
if($username.Contains('\')){
|
||||
$domain, $username = $username.Split('\')
|
||||
}
|
||||
if($username.Contains('@')){
|
||||
$username, $domain = $username.Split('@')
|
||||
}
|
||||
$keyName = "$vip`:$domain`:$username"
|
||||
if(!$passwd){
|
||||
__writeLog "Prompting for Password"
|
||||
$secureString = Read-Host -Prompt "Enter password for $username at $vip" -AsSecureString
|
||||
$passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))
|
||||
}
|
||||
if($PSVersionTable.Platform -eq 'Unix'){
|
||||
# Unix
|
||||
$keyFile = "$CONFDIR/$keyName"
|
||||
$key = New-AesKey
|
||||
$key | Out-File $keyFile
|
||||
Protect-CohesityAPIPassword $key $passwd | Out-File $keyFile -Append
|
||||
}else{
|
||||
# Windows
|
||||
$securePassword = ConvertTo-SecureString -String $passwd -AsPlainText -Force
|
||||
$encryptedPasswordText = $securePassword | ConvertFrom-SecureString
|
||||
if(!(Test-Path $registryPath)){
|
||||
New-Item -Path $registryPath -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path "$registryPath" -Name "$keyName" -Value "$encryptedPasswordText"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# security functions ==============================================================================
|
||||
|
||||
function New-AesManagedObject($key, $IV){
|
||||
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
|
||||
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
|
||||
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
|
||||
$aesManaged.BlockSize = 128
|
||||
$aesManaged.KeySize = 256
|
||||
if($IV){
|
||||
if($IV.getType().Name -eq "String"){
|
||||
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
|
||||
}else{
|
||||
$aesManaged.IV = $IV
|
||||
}
|
||||
}
|
||||
if($key){
|
||||
if($key.getType().Name -eq "String") {
|
||||
$aesManaged.Key = [System.Convert]::FromBase64String($key)
|
||||
}else{
|
||||
$aesManaged.Key = $key
|
||||
}
|
||||
}
|
||||
$aesManaged
|
||||
}
|
||||
|
||||
function New-AesKey() {
|
||||
$aesManaged = New-AesManagedObject
|
||||
$aesManaged.GenerateKey()
|
||||
[System.Convert]::ToBase64String($aesManaged.Key)
|
||||
}
|
||||
|
||||
function Protect-CohesityAPIPassword($key, $unencryptedString) {
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
|
||||
$aesManaged = New-AesManagedObject $key
|
||||
$encryptor = $aesManaged.CreateEncryptor()
|
||||
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
|
||||
[byte[]] $fullData = $aesManaged.IV + $encryptedData
|
||||
$aesManaged.Dispose()
|
||||
[System.Convert]::ToBase64String($fullData)
|
||||
}
|
||||
|
||||
function Unprotect-CohesityAPIPassword($key, $encryptedStringWithIV) {
|
||||
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
|
||||
$IV = $bytes[0..15]
|
||||
$aesManaged = New-AesManagedObject $key $IV
|
||||
$decryptor = $aesManaged.CreateDecryptor();
|
||||
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
|
||||
$aesManaged.Dispose()
|
||||
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
|
||||
}
|
||||
|
||||
# developer tools =================================================================================
|
||||
|
||||
function saveJson($object, $jsonFile = './debug.json'){
|
||||
$object | ConvertTo-Json -Depth 99 | out-file -FilePath $jsonFile
|
||||
}
|
||||
|
||||
function loadJson($jsonFile = './debug.json'){
|
||||
return Get-Content $jsonFile | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function json2code($json = '', $jsonFile = '', $psFile = 'myObject.ps1'){
|
||||
|
||||
if($jsonFile -ne ''){
|
||||
$json = (Get-Content $jsonFile) -join "`n"
|
||||
}
|
||||
|
||||
$json = $json | ConvertFrom-Json | ConvertTo-Json -Depth 99
|
||||
|
||||
$pscode = ''
|
||||
foreach ($line in $json.split("`n")) {
|
||||
$line = $line.TrimEnd()
|
||||
# preserve end of line character
|
||||
$finalEntry = $true
|
||||
if ($line[-1] -eq ',') {
|
||||
$finalEntry = $false
|
||||
$line = $line -replace ".$"
|
||||
}
|
||||
|
||||
# key value delimiter :
|
||||
$key, $value = $line.split(':', 2)
|
||||
|
||||
# line is braces only
|
||||
$key = $key.Replace('{', '@{').Replace('[','@(').Replace(']', ')')
|
||||
|
||||
if ($value) {
|
||||
$value = $value.trim()
|
||||
|
||||
# value is quoted text
|
||||
if ($value[0] -eq '"') {
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# value is opening { brace
|
||||
elseif ('{' -eq $value) {
|
||||
$value = $value.Replace('{', '@{')
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# value is opening [ list
|
||||
elseif ('[' -eq $value) {
|
||||
$value = $value.Replace('[', '@(')
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# empty braces
|
||||
elseif ('{}' -eq $value) {
|
||||
$value = '@{}'
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# empty list
|
||||
elseif ('[]' -eq $value) {
|
||||
$value = '@()'
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# value is opening ( list
|
||||
elseif ('[' -eq $value) {
|
||||
$value = $value.Replace('[', '@(')
|
||||
$line = "$key = $value"
|
||||
}
|
||||
|
||||
# value is a boolean
|
||||
elseif ($value -eq 'true') {
|
||||
$line = "$key = " + '$true'
|
||||
}
|
||||
|
||||
elseif ($value -eq 'false') {
|
||||
$line = "$key = " + '$false'
|
||||
}
|
||||
|
||||
# null
|
||||
elseif ($value -eq 'null') {
|
||||
$line = "$key = " + '$null'
|
||||
}
|
||||
else {
|
||||
|
||||
# value is numeric
|
||||
if ($value -as [long] -or $value -eq '0') {
|
||||
$line = "$($key) = $value"
|
||||
}
|
||||
else {
|
||||
|
||||
# delimeter : was inside of quotes
|
||||
$line = "$($key):$($value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
# was no value on this line
|
||||
$line = $key
|
||||
}
|
||||
|
||||
# replace end of line character ;
|
||||
if (! $finalEntry) {
|
||||
$line = "$line;"
|
||||
}
|
||||
|
||||
$pscode += "$line`n"
|
||||
}
|
||||
|
||||
$pscode = '$myObject = ' + $pscode
|
||||
|
||||
$pscode | out-file $psFile
|
||||
return $pscode
|
||||
}
|
||||
|
||||
# add a property
|
||||
function setApiProperty{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True)][string]$name,
|
||||
[Parameter(Mandatory = $True)][System.Object]$value,
|
||||
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
|
||||
)
|
||||
if(! $object.PSObject.Properties[$name]){
|
||||
$object | Add-Member -MemberType NoteProperty -Name $name -Value $value
|
||||
}else{
|
||||
$object.$name = $value
|
||||
}
|
||||
}
|
||||
|
||||
# delete a propery
|
||||
function delApiProperty{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True)][string]$name,
|
||||
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
|
||||
)
|
||||
$object.PSObject.Members.Remove($name)
|
||||
}
|
||||
|
||||
# show properties of an object
|
||||
function showProps{
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]$obj,
|
||||
[Parameter()]$parent = 'myobject',
|
||||
[Parameter()]$search = $null
|
||||
)
|
||||
if($obj.getType().Name -eq 'String' -or $obj.getType().Name -eq 'Int64'){
|
||||
if($null -ne $search){
|
||||
if($parent.ToLower().Contains($search) -or ($obj.getType().Name -eq 'String' -and $obj.ToLower().Contains($search))){
|
||||
"$parent = $obj"
|
||||
}
|
||||
}else{
|
||||
"$parent = $obj"
|
||||
}
|
||||
|
||||
}else{
|
||||
foreach($prop in $obj.PSObject.Properties | Sort-Object -Property Name){
|
||||
if($($prop.Value.GetType().Name) -eq 'PSCustomObject'){
|
||||
$thisObj = $prop.Value
|
||||
showProps $thisObj "$parent.$($prop.Name)" $search
|
||||
}elseif($($prop.Value.GetType().Name) -eq 'Object[]'){
|
||||
$thisObj = $prop.Value
|
||||
$x = 0
|
||||
foreach($item in $thisObj){
|
||||
showProps $thisObj[$x] "$parent.$($prop.Name)[$x]" $search
|
||||
$x += 1
|
||||
}
|
||||
}else{
|
||||
if($null -ne $search){
|
||||
if($prop.Name.ToLower().Contains($search.ToLower()) -or ($prop.Value.getType().Name -eq 'String' -and $prop.Value.ToLower().Contains($search.ToLower()))){
|
||||
"$parent.$($prop.Name) = $($prop.Value)"
|
||||
}
|
||||
}else{
|
||||
"$parent.$($prop.Name) = $($prop.Value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# convert syntax to python
|
||||
function py($p){
|
||||
$py = $p.replace("$","").replace("].","]['").replace(".","']['")
|
||||
if($py[-1] -ne ']'){
|
||||
$py += "']"
|
||||
}
|
||||
$py
|
||||
}
|
||||
|
||||
|
||||
# self updater
|
||||
function cohesityAPIversion([switch]$update){
|
||||
if($update){
|
||||
$repoURL = 'https://raw.githubusercontent.com/bseltz-cohesity/scripts/master/powershell'
|
||||
(Invoke-WebRequest -Uri "$repoURL/cohesity-api/cohesity-api.ps1").content | Out-File -Force cohesity-api.ps1; (Get-Content cohesity-api.ps1) | Set-Content cohesity-api.ps1
|
||||
Write-Host "Cohesity-API version updated! Please restart PowerShell"
|
||||
}else{
|
||||
Write-Host "Cohesity-API version $versionCohesityAPI" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# paged view list
|
||||
function getViews([switch]$includeInactive){
|
||||
$myViews = @()
|
||||
$views = $null
|
||||
while(! $views){
|
||||
if($includeInactive){
|
||||
$views = api get views?includeInactive=true
|
||||
}else{
|
||||
$views = api get views
|
||||
}
|
||||
}
|
||||
$myViews += $views.views
|
||||
$lastResult = $views.lastResult
|
||||
while(! $lastResult){
|
||||
$lastViewId = $views.views[-1].viewId
|
||||
$views = $null
|
||||
while(! $views){
|
||||
if($includeInactive){
|
||||
$views = api get "views?maxViewId=$lastViewId&includeInactive=true"
|
||||
}else{
|
||||
$views = api get views?maxViewId=$lastViewId
|
||||
}
|
||||
}
|
||||
$lastResult = $views.lastResult
|
||||
$myViews += $views.views
|
||||
}
|
||||
return $myViews
|
||||
}
|
||||
|
||||
# old version history
|
||||
# . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
# 0.06 - Consolidated Windows and Unix versions - June 2018
|
||||
# 0.07 - Added saveJson, loadJson and json2code utility functions - Feb 2019
|
||||
# 0.08 - added -prompt to prompt for password rather than save - Mar 2019
|
||||
# 0.09 - added setApiProperty / delApiProperty - Apr 2019
|
||||
# 0.10 - added $REPORTAPIERRORS constant - Apr 2019
|
||||
# 0.11 - added storePassword function and username parsing - Aug 2019
|
||||
# 0.12 - added -password to apiauth function - Oct 2019
|
||||
# 0.13 - added showProps function - Nov 2019
|
||||
# 0.14 - added storePasswordFromInput function - Dec 2019
|
||||
# 0.15 - added support for PS Core on Windows - Dec 2019
|
||||
# 0.16 - added ServicePoint connection workaround - Jan 2020
|
||||
# 0.17 - fixed json2code line endings on Windows - Jan 2020
|
||||
# 0.18 - added REINVOKE - Jan 2020
|
||||
# 0.19 - fixed password encryption for PowerShell 7.0 - Mar 2020
|
||||
# 0.20 - refactored, added apipwd, added helios access - Mar 2020
|
||||
# 0.21 - helios changes - Mar 2020
|
||||
# 0.22 - added password file storage - Apr 2020
|
||||
# 0.23 - added self updater - Apr 2020
|
||||
# 0.24 - added delete with body - Apr 2020
|
||||
# 0.25 - added paged view list - Apr 2020
|
||||
# 0.26 - added support for tenants - May 2020
|
||||
# 0.27 - added support for Iris API Key - May 2020
|
||||
# 0.28 - added reprompt for password, debug log - June 2020
|
||||
# 0.29 - update storePasswordInFile - June 2020
|
||||
# 2020.06.04 - updated version numbering - June 2020
|
||||
# 2020.06.16 - improved REINVOKE - June 2020
|
||||
# 2020-06.25 - added API v2 support (-version 2) or (-v2)
|
||||
# 2020.07.08 - removed timout
|
||||
# 2020.07.20 - fixed dateToUsecs for international date formats
|
||||
# 2020.07.30 - quiet ssl handler
|
||||
# 2020.08.08 - fixed timezone issue
|
||||
# 2020.10.02 - set PROMPTFORPASSWORDCHANGE to false
|
||||
# 2020.10.05 - retired REINVOKE
|
||||
# 2020.10.06 - exit script when attempting unauthenticated api call
|
||||
# 2020.10.13 - fixed timeAgo function for i14n
|
||||
# . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Starter pipeline
|
||||
# Start with a minimal pipeline that you can customize to build and deploy your code.
|
||||
# Add steps that build, run tests, deploy, and more:
|
||||
# https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
- main
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
|
||||
variables:
|
||||
fName: 'Cohesity-Package'
|
||||
pyEnv: $(System.DefaultWorkingDirectory)/python
|
||||
|
||||
steps:
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: rename@0
|
||||
inputs:
|
||||
Command: 'rename'
|
||||
SourceFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
|
||||
NewName: '$(fName).zip'
|
||||
|
||||
- upload: $(Build.ArtifactStagingDirectory)/$(fName).zip
|
||||
displayName: 'Upload Package'
|
||||
artifact: drop
|
||||
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import sys
|
||||
import certifi
|
||||
import urllib3
|
||||
import time
|
||||
import pymsteams
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as serviceNow
|
||||
|
||||
debug=0
|
||||
failureLimit=1
|
||||
iteration=0
|
||||
errorCount = 0
|
||||
|
||||
itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
|
||||
|
||||
try:
|
||||
# Create the API object for Cohesity
|
||||
mdn = cohesity.API('itdmdndpc01.nd.gov')
|
||||
mdnToken = mdn.GetAuthToken()
|
||||
mdn.UpdateHeaders(mdnToken['accessToken'])
|
||||
|
||||
# Create the API object for ServiceNow
|
||||
ticketGenerator = serviceNow.API('northdakota.service-now.com')
|
||||
|
||||
# Get all SQL resgistered SQL servers from Cohesity
|
||||
vms = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?environments=kSQL")
|
||||
|
||||
# Loop through all servers and check for health status issues
|
||||
for vm in vms['rootNodes']:
|
||||
for check in vm['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
|
||||
if check['resultType'] == "kFail":
|
||||
errorCount += 1
|
||||
# If/Else statement for Debug only, added iteration limit to prevent debug of multiple servers at once
|
||||
if (debug == 1) and (iteration < failureLimit):
|
||||
print("Hostname: " + vm['rootNode']['name'])
|
||||
print("NodeID: " + str(vm['rootNode']['id']))
|
||||
print(check['resultType'] + ": " + check['userMessage'])
|
||||
|
||||
iteration += 1
|
||||
|
||||
#Try to refresh the source to see if the error goes away
|
||||
print("Attempting to refresh " + vm['rootNode']['name'])
|
||||
mdn.RefreshSource(vm['rootNode']['id'])
|
||||
time.sleep(15)
|
||||
|
||||
#Pull the source registration information again and output
|
||||
refreshedData = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?ids=" + str(vm['rootNode']['id']))
|
||||
|
||||
print("Refreshed Data")
|
||||
#print(json.dumps(refreshedData,indent=4))
|
||||
|
||||
for refreshedVM in refreshedData['rootNodes']:
|
||||
persistantError = 0
|
||||
for refreshedCheck in refreshedVM['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
|
||||
if (refreshedCheck['resultType']=="kFail"):
|
||||
print("Hostname: " + refreshedVM['rootNode']['name'])
|
||||
print("NodeID: " + str(refreshedVM['rootNode']['id']))
|
||||
print(refreshedCheck['resultType'] + ": " + refreshedCheck['userMessage'])
|
||||
persistantError += 1
|
||||
|
||||
# Open a service now ticket for any server that has a health issue
|
||||
shortDesc = 'Cohesity: SQL Registration Error for ' + vm['rootNode']['name']
|
||||
description = 'Please assign this ticket to the SQL Admins to invesigate the following issue(s)\n' + check['userMessage']
|
||||
print("Opening Ticket")
|
||||
|
||||
if persistantError == 0:
|
||||
print("Refreshing the source " + vm['rootNode']['name'] + " resolved the registration error.")
|
||||
|
||||
# Already in a 'failure' if statement, else is only for non-debug operation
|
||||
elif (debug == 0):
|
||||
#Try to refresh the source to see if the error goes away
|
||||
mdn.RefreshSource(vm['rootNode']['id'])
|
||||
time.sleep(15)
|
||||
|
||||
#Pull the source registration information again and output
|
||||
refreshedData = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?ids=" + str(vm['rootNode']['id']))
|
||||
|
||||
for refreshedVM in refreshedData['rootNodes']:
|
||||
persistantError = 0
|
||||
for refreshedCheck in refreshedVM['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
|
||||
if (refreshedCheck['resultType']=="kFail"):
|
||||
# Open a service now ticket for any server that has a health issue
|
||||
shortDesc = 'Cohesity: SQL Registration Error for ' + vm['rootNode']['name']
|
||||
description = 'Please assign this ticket to the SQL Admins to invesigate the following issue(s)\n' + check['userMessage']
|
||||
snResponse=ticketGenerator.submitTicket(shortDesc, description)
|
||||
|
||||
itdTeamsMessage.text("SQL Registration error found. A ServiceNow ticket has been opened to investigate " + vm['rootNode']['name'])
|
||||
itdTeamsMessage.send()
|
||||
|
||||
# Ticket has been opened, break the loop to prevent opening multiple tickets for the same host.
|
||||
break
|
||||
|
||||
#End refreshed error/message checks loop
|
||||
#End refreshed VM data loop
|
||||
#End debug if/elif block
|
||||
# End if failure message found
|
||||
# End for loop of all error/warning message checks
|
||||
# End for loop of all VMs
|
||||
|
||||
itdTeamsMessage.text("Finished checking Cohesity SQL sources for registration errors, found: " + str(errorCount))
|
||||
itdTeamsMessage.send()
|
||||
|
||||
except OSError as cohesityError:
|
||||
print('Cohesity Error: ' + cohesityError)
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time,yaml
|
||||
import re
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--server', '-s', type=str, action='store')
|
||||
parser.add_argument('--vCenter', '-v', type=str, action='store')
|
||||
return (parser.parse_args())
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
with open("./exemptions.yml", "r") as exemptionJobData:
|
||||
try:
|
||||
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
|
||||
except:
|
||||
print("Unable to load exemptions file.")
|
||||
|
||||
exemptServers = []
|
||||
|
||||
for i in exemptionJobData:
|
||||
for e in exemptionJobData[i]['vms']:
|
||||
exemptServers.append([exemptionJobData[i]['id'],e.lower()])
|
||||
|
||||
for entry in exemptServers:
|
||||
parent = entry[0]
|
||||
pattern = entry[1]
|
||||
|
||||
if (parent == int(args.vCenter) and re.search(pattern, args.server)):
|
||||
print("Found match with pattern: " + pattern)
|
||||
@@ -0,0 +1,62 @@
|
||||
import sys
|
||||
|
||||
# Import NDIT storage classes
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as serviceNow
|
||||
|
||||
def main():
|
||||
|
||||
# Instantiate the ServiceNow API
|
||||
svcNowInterface = serviceNow.API('northdakota.service-now.com')
|
||||
|
||||
# Instantiate Cohesity APIs
|
||||
mdnCluster = cohesity.API('itdmdndpc01.nd.gov')
|
||||
|
||||
clusters = [mdnCluster]
|
||||
|
||||
replicationFailures = []
|
||||
sqlFailures = []
|
||||
backupFailures = []
|
||||
undetermined = []
|
||||
|
||||
for cluster in clusters:
|
||||
cluster.Authenticate()
|
||||
|
||||
alertID = 0
|
||||
openAlerts = cluster.GetFilteredRequest("/public/alerts","?alertStateList=kOpen&maxAlerts=1000")
|
||||
if len(openAlerts) > 1:
|
||||
for alert in openAlerts:
|
||||
|
||||
if (alert['severity'] != "kInfo"):
|
||||
# Open a unique ticket for every alert
|
||||
|
||||
if (alert['alertCategory'] == "kRemoteReplication"):
|
||||
replicationFailures.append(alert)
|
||||
continue
|
||||
|
||||
if (alert['alertCategory'] == "kBackupRestore"):
|
||||
for item in alert['propertyList']:
|
||||
if (item['value'] == "kSQL"):
|
||||
sqlFailures.append(alert)
|
||||
continue
|
||||
|
||||
|
||||
shortDescription = "Backup Failure on " + str(cluster.GetClusterName()) + ": " + alert['alertDocument']['alertName']
|
||||
longDescription = alert['alertDocument']['alertCause']
|
||||
|
||||
print("Opening incident for Cohesity alert:" + str(alertID))
|
||||
print("\tShort Description: " + shortDescription)
|
||||
print("\tLong Description: " + longDescription)
|
||||
alertID += 1
|
||||
#End if alert['severity'] != kInfo
|
||||
#End for alert in alerts
|
||||
# End if len(openAlerts) > 1
|
||||
#End for cluster in clusters
|
||||
|
||||
print("Total replication failures: " + str(len(replicationFailures)))
|
||||
print("Total SQL Backup failures: " + str(len(sqlFailures)))
|
||||
#End main()
|
||||
|
||||
# Run the program
|
||||
main()
|
||||
@@ -0,0 +1,372 @@
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
global tagid
|
||||
tagid = None
|
||||
|
||||
global tagName
|
||||
tagName = None
|
||||
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--tag', '-t', type=str, action='store')
|
||||
parser.add_argument('--group', '-g', type=str, action='store')
|
||||
parser.add_argument('--action', '-a', type=str, action='store')
|
||||
parser.add_argument('--list', '-l', type=str, action='store')
|
||||
return (parser.parse_args())
|
||||
|
||||
def GetTagID(sources):
|
||||
globalList = globals()
|
||||
|
||||
for source in sources:
|
||||
if globalList['tagid'] is None:
|
||||
GetNode(sources)
|
||||
|
||||
def GetNode(record):
|
||||
globalList = globals()
|
||||
|
||||
# A non 'node' element was found, check each child key to see if one contains 'kTag'
|
||||
if "protectionSource" in record:
|
||||
if "vmWareProtectionSource" in record['protectionSource']:
|
||||
if record['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
|
||||
|
||||
# Tag elements have been found, look for the correct tag and assign the id to the global variable
|
||||
if record['protectionSource']['vmWareProtectionSource']['name'] == globalList['tagName']:
|
||||
|
||||
# Tag was found based on the name
|
||||
globalList['tagid'] = record['protectionSource']['id']
|
||||
|
||||
# Recurse through all 'nodes' elements looking for kTag
|
||||
if "nodes" in record:
|
||||
for node in record['nodes']:
|
||||
|
||||
# The tag hasn't been found, search the next record
|
||||
if globalList['tagid'] is None:
|
||||
GetNode(node)
|
||||
|
||||
# The tag was found and the global id value was set, stop iterating.
|
||||
else:
|
||||
break
|
||||
|
||||
def UpdateProtectionGroupExcludes(group, action):
|
||||
globalList = globals()
|
||||
isExcluded = False
|
||||
tagArray = []
|
||||
|
||||
if "excludeVmTagIds" in group:
|
||||
for index in group['excludeVmTagIds']:
|
||||
for tag in index:
|
||||
tagArray.append(tag)
|
||||
if tag == globalList['tagid']:
|
||||
isExcluded = True
|
||||
|
||||
# Add the tag to the exclusion
|
||||
if action == 'add':
|
||||
if isExcluded == True:
|
||||
print(globalList['tagName'] + " tag is already excluded for " + group['name'])
|
||||
else:
|
||||
print("Adding exclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
|
||||
tagArray.append(globalList['tagid'])
|
||||
|
||||
# Remove the tag from the exclusion
|
||||
elif action == 'remove':
|
||||
if isExcluded == True:
|
||||
print("Removing exclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
|
||||
tagArray.remove(globalList['tagid'])
|
||||
else:
|
||||
print(globalList['tagName'] + " tag is not excluded for " + group['name'])
|
||||
|
||||
|
||||
return(tagArray)
|
||||
|
||||
def UpdateProtectionGroupIncludes(group, action):
|
||||
globalList = globals()
|
||||
isIncluded = False
|
||||
|
||||
tagArray = []
|
||||
|
||||
if "vmTagIds" in group:
|
||||
for index in group['vmTagIds']:
|
||||
for tag in index:
|
||||
tagArray.append(tag)
|
||||
if tag == globalList['tagid']:
|
||||
isIncluded = True
|
||||
|
||||
# Add the tag to the inclusion
|
||||
if action == 'add':
|
||||
if isIncluded == True:
|
||||
print(globalList['tagName'] + " tag is already included for " + group['name'])
|
||||
else:
|
||||
print("Adding inclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
|
||||
tagArray.append(globalList['tagid'])
|
||||
|
||||
# Remove the tag from the inclusion
|
||||
elif action == 'remove':
|
||||
if isIncluded == True:
|
||||
print("Removing inclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
|
||||
tagArray.remove(globalList['tagid'])
|
||||
else:
|
||||
print(globalList['tagName'] + " tag is not included for " + group['name'])
|
||||
|
||||
return(tagArray)
|
||||
|
||||
|
||||
# Begin Main
|
||||
globalList = globals()
|
||||
|
||||
sourceFound = False
|
||||
vCenter = None
|
||||
vCenterID = None
|
||||
tagIds = []
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
|
||||
else:
|
||||
globalList['tagName'] = args.tag
|
||||
|
||||
if not args.vcenter:
|
||||
sys.exit("Error: Specify a vCenter with -v parameter")
|
||||
|
||||
if not args.tag:
|
||||
sys.exit("Error: Specify a tag with the -t parameter.")
|
||||
|
||||
if not args.action:
|
||||
sys.exit("Error: Specify an action [add|remove] with -a parameter.")
|
||||
elif args.action != 'add' and args.action != 'remove':
|
||||
sys.exit("Error: Use only 'add' or 'remove' with -a parameter.")
|
||||
|
||||
if not args.list:
|
||||
sys.exit("Error: Specify a list [include|exclude] with -l parameter.")
|
||||
elif args.list != 'exclude' and args.list != 'include':
|
||||
sys.exit("Error: Use only 'include' or 'exclude' with -l parameter.")
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Locate the correct vCenter and capture the id, this will be useful to validate child objects
|
||||
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
|
||||
for source in vmSources:
|
||||
if source['protectionSource']['name'] == args.vcenter:
|
||||
sourceFound = True
|
||||
vCenter = source
|
||||
vCenterID = source['protectionSource']['id']
|
||||
break
|
||||
|
||||
# The vCenter we are looking for was not in the list of kVMware environments, stop processing.
|
||||
if sourceFound == False:
|
||||
sys.exit("Error: " + args.vcenter + " is not registered to " + args.cluster)
|
||||
|
||||
# Look up the tag id based on the tagName and set a global variable when found
|
||||
# IMPROVEMENT TO GET AWAY FROM GLOBAL VAR
|
||||
GetTagID(vCenter)
|
||||
|
||||
if globalList['tagid'] is None:
|
||||
sys.exit("Error: The tag '" + args.tag + "' does not exist in " + vCenter['protectionSource']['name'])
|
||||
|
||||
if args.group:
|
||||
# Replace '@' symbol with '%40' for URL encoding in the REST API
|
||||
protectionGroup = args.group.replace("@","%40")
|
||||
job = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + protectionGroup)
|
||||
|
||||
print(json.dumps(job[0], indent=4))
|
||||
|
||||
if job[0]['parentSourceId'] != vCenterID:
|
||||
sys.exit(job[0]['name'] + " does not belong to source " + vCenter['protectionSource']['name'])
|
||||
else:
|
||||
if (args.list == 'exclude'):
|
||||
excludeTagIds = UpdateProtectionGroupExcludes(job[0], args.action)
|
||||
if "excludeVmTagIds" in job[0]:
|
||||
job[0]['excludeVmTagIds'][0] = excludeTagIds
|
||||
else:
|
||||
job[0].update({"excludeVmTagIds": [ excludeTagIds ]})
|
||||
else:
|
||||
includeTagIds = UpdateProtectionGroupIncludes(job[0], args.action)
|
||||
if "vmTagIds" in job[0]:
|
||||
job[0]['vmTagIds'][0] = includeTagIds
|
||||
else:
|
||||
job[0].update({"includeVmTagIds": [ includeTagIds ]})
|
||||
|
||||
|
||||
# FIX the screw up
|
||||
# Temp code to grab start time from previous runs
|
||||
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job[0]['id']))
|
||||
|
||||
try:
|
||||
hour = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
|
||||
hour = time.strftime("%H", time.localtime(hour))
|
||||
minute = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
|
||||
minute = time.strftime("%M", time.localtime(minute))
|
||||
|
||||
hour = int(hour)
|
||||
minute = int(minute)
|
||||
|
||||
except:
|
||||
hour = 21
|
||||
minute = 00
|
||||
|
||||
job[0]['startTime']['hour'] = hour
|
||||
job[0]['startTime']['minute'] = minute
|
||||
# EndTime
|
||||
|
||||
# Temp code to build indexing
|
||||
job[0].update({"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
}})
|
||||
|
||||
# Temp code to set SLA
|
||||
job[0].update({"incrementalProtectionSlaTimeMins": 480})
|
||||
job[0].update({"fullProtectionSlaTimeMins": 480})
|
||||
|
||||
job[0].update({"abortInBlackoutPeriod": False})
|
||||
job[0].update({"quiesce": False})
|
||||
job[0].update({"qosType": "kBackupHDD"})
|
||||
job[0].update({"environmentParameters":{
|
||||
"vmwareParameters": {
|
||||
"fallbackToCrashConsistent": False,
|
||||
"skipPhysicalRdmDisks": False
|
||||
}
|
||||
}})
|
||||
job[0].update({"cloudParameters":{"failoverToCloud": False}})
|
||||
job[0].update({"leverageStorageSnapshots": False})
|
||||
job[0].update({"leverageStorageSnapshotsForHyperFlex": False})
|
||||
job[0].update({"description": ""})
|
||||
|
||||
# End Fix
|
||||
|
||||
|
||||
# Take acion on group(s)
|
||||
resp = cluster.UpdateVMProtectionJob(job[0])
|
||||
print(resp.content)
|
||||
|
||||
else:
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
for job in uniqueJobs:
|
||||
if "isPaused" in job:
|
||||
if job['isPaused']:
|
||||
continue
|
||||
# If the job is marked for deletion, skip
|
||||
if 'isDeleted' in job:
|
||||
continue
|
||||
|
||||
# If the job does not belong to this vCetner, skip
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
continue
|
||||
|
||||
else:
|
||||
if (args.list == 'exclude'):
|
||||
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
|
||||
|
||||
if "excludeVmTagIds" in job:
|
||||
job['excludeVmTagIds'][0] = excludeTagIds
|
||||
else:
|
||||
job.update({"excludeVmTagIds": [ excludeTagIds ]})
|
||||
|
||||
else:
|
||||
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
|
||||
|
||||
if "vmTagIds" in job:
|
||||
job['vmTagIds'][0] = includeTagIds
|
||||
else:
|
||||
job.update({"includeVmTagIds": [ includeTagIds ]})
|
||||
|
||||
# FIX the screw up
|
||||
# Temp code to grab start time from previous runs
|
||||
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job['id']))
|
||||
|
||||
try:
|
||||
hour = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
|
||||
hour = time.strftime("%H", time.localtime(hour))
|
||||
minute = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
|
||||
minute = time.strftime("%M", time.localtime(minute))
|
||||
|
||||
hour = int(hour)
|
||||
minute = int(minute)
|
||||
|
||||
except:
|
||||
hour = 21
|
||||
minute = 00
|
||||
|
||||
job['startTime']['hour'] = hour
|
||||
job['startTime']['minute'] = minute
|
||||
# EndTime
|
||||
|
||||
# Temp code to build indexing
|
||||
job.update({"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
}})
|
||||
|
||||
# Temp code to set SLA
|
||||
job.update({"incrementalProtectionSlaTimeMins": 480})
|
||||
job.update({"fullProtectionSlaTimeMins": 480})
|
||||
|
||||
job.update({"abortInBlackoutPeriod": False})
|
||||
job.update({"quiesce": False})
|
||||
job.update({"qosType": "kBackupHDD"})
|
||||
job.update({"environmentParameters":{
|
||||
"vmwareParameters": {
|
||||
"fallbackToCrashConsistent": False,
|
||||
"skipPhysicalRdmDisks": False
|
||||
}
|
||||
}})
|
||||
job.update({"cloudParameters":{"failoverToCloud": False}})
|
||||
job.update({"leverageStorageSnapshots": False})
|
||||
job.update({"leverageStorageSnapshotsForHyperFlex": False})
|
||||
job.update({"description": ""})
|
||||
|
||||
|
||||
# Take acion on group(s)
|
||||
print("Updating: " + job['name'])
|
||||
resp = cluster.UpdateVMProtectionJob(job)
|
||||
print(resp.content)
|
||||
@@ -0,0 +1,89 @@
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--job', '-j', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
|
||||
print("\nExample:")
|
||||
print(r"$ export COHESITY_USER=\"NDGOV\jDoe\"")
|
||||
print("$ export COHESITY_PASS=\"1Lik3Jane\"")
|
||||
print("\n python3 <SCRIPT_NAME> -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
|
||||
print("\t -j Cohesity job name")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
|
||||
else:
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
if args.vcenter:
|
||||
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
|
||||
|
||||
for source in vmSources:
|
||||
if source['protectionSource']['name'] == args.vcenter:
|
||||
vCenter = source
|
||||
vCenterID = source['protectionSource']['id']
|
||||
break
|
||||
|
||||
if args.job:
|
||||
# Get the details of a single job
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
|
||||
else:
|
||||
# Get the details of all jobs under a vCenter
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
# Do stuff with the JSON data
|
||||
# Example:
|
||||
if args.pause:
|
||||
pausedJobs = []
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
print("not vcenter")
|
||||
continue
|
||||
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
|
||||
elif "isPaused" in job:
|
||||
if job['isPaused']:
|
||||
pausedJobs.append(job)
|
||||
continue
|
||||
|
||||
print("Pausing: " + job['name'])
|
||||
resp = cluster.PauseJob(job['id'])
|
||||
|
||||
print("The following jobs were previously paused before this operation.")
|
||||
for pJob in pausedJobs:
|
||||
print(pJob['name'])
|
||||
|
||||
if args.resume:
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
continue
|
||||
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
|
||||
print("Resuming: " + job['name'])
|
||||
resp = cluster.ResumeJob(job['id'])
|
||||
@@ -0,0 +1,21 @@
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
mdn = cohesity.API('itdmdndpc01.nd.gov')
|
||||
mdnToken = mdn.GetAuthToken()
|
||||
mdn.UpdateHeaders(mdnToken['accessToken'])
|
||||
|
||||
# Block Update all SQL jobs with new SQL policy
|
||||
sqlPolicyName = mdn.GetFilteredRequest("/public/protectionPolicies", "?names=ITD-SQL")
|
||||
|
||||
sqlProtectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments=kSQL")
|
||||
uniqueJobs = {job['id'] : job for job in sqlProtectionJobs}.values()
|
||||
|
||||
isPaused=bool('false')
|
||||
|
||||
for job in uniqueJobs:
|
||||
resp = mdn.UpdateProtectionJob(job['sourceIds'],job['parentSourceId'], job['name'], sqlPolicyName[0]['id'], job['viewBoxId'], job['id'], isPaused)
|
||||
print(resp.content)
|
||||
#End SQL Policy Update Block
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 cancelJobs.py -c cluster1.domain.tld" )
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if arg.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
# Non Running states
|
||||
finStates = ['kCanceled', 'kSuccess', 'kFailure', 'kWarning']
|
||||
|
||||
for job in uniqueJobs:
|
||||
if 'isDeleted' in job:
|
||||
continue
|
||||
if 'isPaused' in job:
|
||||
continue
|
||||
|
||||
# Get running protections per vmware protection job id, most recent is returned
|
||||
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job['id']))
|
||||
|
||||
if run[0]['backupRun']['status'] not in finStates:
|
||||
# Cancel the running protection job
|
||||
resp = cluster.CancelJob(job['id'],run[0]['backupRun']['jobRunId'])
|
||||
|
||||
# Print the response
|
||||
print(resp.content)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import requests
|
||||
|
||||
def send_automation(_data: dict):
|
||||
_url = 'http://itdnettools.nd.gov/services/automation-tracking.py'
|
||||
_r = requests.post(_url, json=_data)
|
||||
return _r.text
|
||||
|
||||
@@ -0,0 +1,792 @@
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
import urllib3
|
||||
import certifi
|
||||
import os
|
||||
import pytz
|
||||
from datetime import datetime, timedelta, time
|
||||
|
||||
class API(object):
|
||||
def __init__(self, server):
|
||||
|
||||
if not ((os.environ.get('COHESITY_USER')) and (os.environ.get('COHESITY_PASS'))):
|
||||
print("\n*** Environment Variables are not set for authentication! ***")
|
||||
exit()
|
||||
# Get the authorization bearer token
|
||||
self.user = os.environ['COHESITY_USER']
|
||||
self.pwd = os.environ['COHESITY_PASS']
|
||||
if self.user == "svcitdchstyauto":
|
||||
self.domain = "local"
|
||||
else:
|
||||
self.domain = "nd.gov"
|
||||
self.server = server
|
||||
self.base_url = ('https://{}/irisservices/api/v1').format(self.server)
|
||||
self.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
def GetClusterName(self):
|
||||
return self.server
|
||||
|
||||
###################################################### Depricated Methods #################################################
|
||||
def GetAuthToken(self):
|
||||
apiCall = "/public/accessTokens"
|
||||
payload = {
|
||||
"username": self.user,
|
||||
"password": self.pwd,
|
||||
"domain": self.domain
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
token = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload, verify=True)
|
||||
if 'locked' in token.text:
|
||||
print("Account {} is locked".format(self.user))
|
||||
exit(1)
|
||||
if 'expired' in token.text:
|
||||
print("Password for local account {} has expired".format(self.user))
|
||||
exit(1)
|
||||
return(self.FormatData(token))
|
||||
|
||||
###########################################################################################################################
|
||||
|
||||
# Methods under maintenance
|
||||
def FormatData(self, response):
|
||||
jsonData = json.loads(response.text)
|
||||
return (jsonData)
|
||||
|
||||
def GetClusterName(self):
|
||||
return(self.server)
|
||||
|
||||
def Authenticate(self):
|
||||
apiCall = "/public/accessTokens"
|
||||
payload = {
|
||||
"username": self.user,
|
||||
"password": self.pwd,
|
||||
"domain": self.domain
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
authResponse = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload, verify=True)
|
||||
token = self.FormatData(authResponse)
|
||||
|
||||
if (token['accessToken']):
|
||||
self.headers.update({'Authorization': 'Bearer ' + token['accessToken']})
|
||||
return()
|
||||
else:
|
||||
print("ERROR: Unable to authenticate to " + self.server)
|
||||
|
||||
def UpdateHeaders(self, token):
|
||||
self.headers.update({'Authorization': 'Bearer ' + token})
|
||||
|
||||
def GetRelativeTimestamp(self, d, h, m, s):
|
||||
|
||||
# Timezone
|
||||
tz = pytz.timezone("America/Chicago")
|
||||
|
||||
# Current date
|
||||
date = datetime.now(tz).date()
|
||||
|
||||
# Convert to yesterday
|
||||
start_date = date + timedelta(days=d)
|
||||
|
||||
# Set time to 1700
|
||||
start_time = datetime(
|
||||
year = start_date.year,
|
||||
month = start_date.month,
|
||||
day = start_date.day,
|
||||
hour = h,
|
||||
minute = m,
|
||||
second = s
|
||||
)
|
||||
|
||||
# Add timezone info to the start time
|
||||
start_time = tz.localize(start_time)
|
||||
|
||||
# Convert to UTC
|
||||
start_utc_time = start_time.astimezone(pytz.utc)
|
||||
|
||||
# Convert to unix epoch microseconds
|
||||
start_epoch_ms = int(start_utc_time.timestamp() * 1000000)
|
||||
|
||||
return(start_epoch_ms)
|
||||
|
||||
def GetRandomStartTime(self):
|
||||
hours = [17, 18, 19, 20, 21, 22, 23, 00, 1, 2, 3, 4, 5, 6]
|
||||
min = [0, 15, 30, 45]
|
||||
rHour = random.choice(hours)
|
||||
rMin = random.choice(min)
|
||||
sTime=[rHour,rMin]
|
||||
|
||||
return sTime
|
||||
|
||||
#################### DELETE Methods ####################
|
||||
def DeleteJob(self, url):
|
||||
apiCall = url
|
||||
payload = {
|
||||
"deleteSnapshots": True
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
data = requests.delete(self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return(data)
|
||||
|
||||
|
||||
|
||||
#################### SET Methods #######################
|
||||
|
||||
def ResolveAlert(self, alert_id, snow_id):
|
||||
apiCall = "/public/alertResolutions"
|
||||
payload = {
|
||||
"alertIdList": [
|
||||
alert_id
|
||||
],
|
||||
"resolutionDetails": {
|
||||
"resolutionDetails":"See ServiceNow",
|
||||
"resolutionSummary": snow_id
|
||||
}
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
#################### GET Methods #######################
|
||||
|
||||
def GetRequest(self, url):
|
||||
apiCall = url
|
||||
payload = {}
|
||||
encoded_payload = json.dumps(payload)
|
||||
data = requests.request("GET", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return(self.FormatData(data))
|
||||
|
||||
def GetRequestV2(self, url):
|
||||
base_url = ('https://{}/v2').format(self.server)
|
||||
apiCall = url
|
||||
payload = {}
|
||||
encoded_payload = json.dumps(payload)
|
||||
data = requests.request("GET", base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return(self.FormatData(data))
|
||||
|
||||
def GetFilteredRequest(self, url, filters):
|
||||
apiCall = url + filters
|
||||
payload = {}
|
||||
encoded_payload = json.dumps(payload)
|
||||
data = requests.request("GET", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return(self.FormatData(data))
|
||||
|
||||
def GetPhysicalsources(self):
|
||||
apiCall = "/public/protectionSources/registrationInfo?isDeleted=False&environments=kPhysical"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetSQLsources(self):
|
||||
apiCall = "/public/protectionSources/registrationInfo?isDeleted=False&environments=kSQL"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetSQLjobs(self):
|
||||
apiCall = "/public/protectionJobs/?isDeleted=False&environments=kSQL"
|
||||
return(self.FormatData(self.GetRequest(apiCall)))
|
||||
|
||||
def GetVMsources(self):
|
||||
apiCall = "/public/protectionSources?environments=kVMware"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetVMjobs(self):
|
||||
apiCall = "/public/protectionJobs?environments=kVMware"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetDiskAlerts(self, startTime):
|
||||
apiCall = "/public/alerts?alertStateList=kOpen&alertCategoryList=kDisk&startDateUsecs=" + str(startTime)
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetProtectedVMobjects(self, hypervisorID):
|
||||
apiCall = "/public/protectionSources/protectedObjects?environment=kVMware&id=" + str(hypervisorID)
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetProtectionPolicyByName(self, policyName):
|
||||
apiCall = "/public/protectionPolicies/?name=" + policyName
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetVMObjects(self, hypervisorID):
|
||||
apiCall = "/public/protectionSources/virtualMachines?vCenterId=" + str(hypervisorID)
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetAZsources(self):
|
||||
apiCall = "/public/protectionSources?environments=kAzure"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetAZNativeJobs(self):
|
||||
apiCall = "/public/protectionJobs?environments=kAzureNative"
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetAZObjects(self, hypervisorID):
|
||||
apiCall = "/public/protectionSources?environments=kAzure&id=" + str(hypervisorID)
|
||||
data = self.GetRequest(apiCall)
|
||||
azureVMs = []
|
||||
for node in data[0]['nodes']:
|
||||
if 'nodes' in node:
|
||||
for subNode in node['nodes']:
|
||||
if 'protectionSource' in subNode:
|
||||
if 'azureProtectionSource' in subNode['protectionSource']:
|
||||
if "type" in subNode['protectionSource']['azureProtectionSource']:
|
||||
if subNode['protectionSource']['azureProtectionSource']['type'] == 'kVirtualMachine':
|
||||
azureVMs.append(subNode['protectionSource'])
|
||||
return(azureVMs)
|
||||
|
||||
def GetProtectedAZobjects(self, hypervisorID):
|
||||
apiCall = "/public/protectionSources/protectedObjects?environment=kAzure&id=" + str(hypervisorID)
|
||||
return(self.GetRequest(apiCall))
|
||||
|
||||
def GetPhysicalJobs(self):
|
||||
apiCalll = "/public/protectionJobs?isDeleted=False&environments=kPhysicalFiles"
|
||||
return(self.FormatData(self.GetRequest(apiCall)))
|
||||
|
||||
def GetProtectionSourceId(self, hostname):
|
||||
sourceRegistered = False
|
||||
hostname = hostname.lower()
|
||||
sources = self.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
|
||||
|
||||
for source in sources['rootNodes']:
|
||||
if (source['rootNode']['name']).lower() == hostname:
|
||||
sourceId = source['rootNode']['id']
|
||||
sourceRegistered = True
|
||||
break
|
||||
if sourceRegistered:
|
||||
return(sourceId)
|
||||
else:
|
||||
sys.exit("Source not registered")
|
||||
|
||||
def GetVcenterTagId(self, vCenterId, tagName):
|
||||
tagFound=0
|
||||
for record in vCenterId[0]['nodes'][0]['nodes']:
|
||||
if "protectionSource" in record:
|
||||
if "name" in record['protectionSource']:
|
||||
# Check if tagName matches AppName
|
||||
if record['protectionSource']['name'] == "AppName":
|
||||
for tagRecord in record['nodes']:
|
||||
if tagRecord['protectionSource']['name'] == tagName and tagRecord['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
|
||||
tagId = tagRecord['protectionSource']['id']
|
||||
tagFound = 1
|
||||
break
|
||||
|
||||
# Check if tagName matches DTAP (Development, Test, Acceptable, Production)
|
||||
if record['protectionSource']['name'] == "DTAP":
|
||||
for tagRecord in record['nodes']:
|
||||
if tagRecord['protectionSource']['name'] == tagName and tagRecord['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
|
||||
tagId = tagRecord['protectionSource']['id']
|
||||
tagFound = 1
|
||||
break
|
||||
if tagFound == 1:
|
||||
break
|
||||
if tagFound == 1:
|
||||
return tagId
|
||||
else:
|
||||
return None
|
||||
|
||||
def GetAzureTagId(self, azSubId, tagName):
|
||||
tagFound = 0
|
||||
for record in azSubId[0]['nodes']:
|
||||
if "protectionSource" in record:
|
||||
if "name" in record['protectionSource']:
|
||||
if record['protectionSource']['name'] == "ApplicationName#~#" + tagName:
|
||||
if record['protectionSource']['azureProtectionSource']['type'] == "kTag":
|
||||
tagId = record['protectionSource']['id']
|
||||
tagFound = 1
|
||||
break
|
||||
if tagFound == 1:
|
||||
break
|
||||
if tagFound == 1:
|
||||
return tagId
|
||||
else:
|
||||
return None
|
||||
|
||||
def GetProtectionJobByHost(self, hostname):
|
||||
jobFound = False
|
||||
jobs = self.GetFilteredRequest("/public/protectionJobs", "?isDeleted=False&environments=kPhysicalFiles,kSQL")
|
||||
sourceId = self.GetProtectionSourceId(hostname)
|
||||
for job in jobs:
|
||||
for source in job['sourceIds']:
|
||||
if source == sourceId:
|
||||
jobName = job['name']
|
||||
jobFound = True
|
||||
break
|
||||
|
||||
# Job was found break out of the jobs loop
|
||||
if jobFound:
|
||||
break
|
||||
|
||||
if jobFound:
|
||||
return(job)
|
||||
else:
|
||||
return(-1)
|
||||
|
||||
################### Registration Methods ##################
|
||||
|
||||
def RemoveProtectionSource(self, sourceId):
|
||||
apiCall = "/public/protectionSources/" + str(sourceId)
|
||||
response = requests.request("DELETE", self.base_url + apiCall, headers=self.headers)
|
||||
return(response)
|
||||
|
||||
def RegisterPhysical(self, hostName):
|
||||
apiCall = "/public/protectionSources/register"
|
||||
payload = {
|
||||
"environment": "kPhysical",
|
||||
"physicalType": "kHost",
|
||||
"forceRegister" : True,
|
||||
# The API will error if hostType is not specified; however during the registration process Cohesity will register
|
||||
# the correct type even if the incorrect type was given in the payload.
|
||||
"hostType": "kWindows",
|
||||
"endpoint": hostName
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return(self.FormatData(response))
|
||||
|
||||
def RegisterSQL(self, sourceID):
|
||||
apiCall = "/public/protectionSources/applicationServers"
|
||||
payload = {
|
||||
"applications": [
|
||||
"kSQL"
|
||||
],
|
||||
"hasPersistentAgent": bool("true"),
|
||||
"protectionSourceId": sourceID
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (self.FormatData(response))
|
||||
|
||||
def RefreshSource(self, sourceID):
|
||||
apiCall = "/public/protectionSources/refresh/" + str(sourceID)
|
||||
payload = {}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers)
|
||||
return
|
||||
|
||||
################ Protection Job Methods #######################
|
||||
|
||||
def PauseJob(self, jobId):
|
||||
apiCall = "/public/protectionJobState/" + str(jobId)
|
||||
payload = {
|
||||
"pause": True,
|
||||
"pauseReason": 0
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def ResumeJob(self, jobId):
|
||||
apiCall = "/public/protectionJobState/" + str(jobId)
|
||||
payload = {
|
||||
"pause": False,
|
||||
"pauseReason": 0
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CancelJob(self, jobId, runId):
|
||||
apiCall = "/public/protectionRuns/cancel/" + str(jobId)
|
||||
payload = {
|
||||
"jobRunId": runId
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def UpdateVMProtectionJob(self, job):
|
||||
apiCall = "/public/protectionJobs/" + str(job['id'])
|
||||
payload = job
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def UpdateProtectionJob(self, job):
|
||||
apiCall = "/public/protectionJobs/" + str(job['id'])
|
||||
payload = job
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def UpdateAZprotectionJob(self, job):
|
||||
base_url = ('https://{}/v2').format(self.server)
|
||||
apiCall = "/data-protect/protection-groups/7780085755317378:1694019083558:" + str(job['id'])
|
||||
payload = job
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("PUT", base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CreateVMProtectionJob(self, pgName, parentId, tagId, excludeTag, polId):
|
||||
apiCall = "/public/protectionJobs/"
|
||||
sTime=self.GetRandomStartTime()
|
||||
payload = {
|
||||
"name": pgName,
|
||||
"description": "",
|
||||
"environment": "kVMware",
|
||||
# NDIT - Bronze Policy
|
||||
"policyId": polId,
|
||||
#"policyId": "4847477517838800:1610060943809:6402",
|
||||
# Default Storage Domain
|
||||
"viewBoxId": 198,
|
||||
"parentSourceId": parentId,
|
||||
"vmTagIds": [
|
||||
[
|
||||
tagId
|
||||
]
|
||||
],
|
||||
"excludeVmTagIds": [
|
||||
[
|
||||
excludeTag
|
||||
]
|
||||
],
|
||||
"startTime": {
|
||||
"hour": sTime[0],
|
||||
"minute": sTime[1]
|
||||
},
|
||||
"timezone": "America/Chicago",
|
||||
"incrementalProtectionSlaTimeMins": 480,
|
||||
"fullProtectionSlaTimeMins": 480,
|
||||
"priority": "kLow",
|
||||
"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
"abortInBlackoutPeriod": False,
|
||||
"quiesce": False,
|
||||
"qosType": "kBackupHDD",
|
||||
"environmentParameters":{
|
||||
"vmwareParameters":{
|
||||
"fallbackToCrashConsistent": False,
|
||||
"skipPhysicalRdmDisks": False
|
||||
}
|
||||
},
|
||||
"cloudParameters": {
|
||||
"failoverToCloud": False
|
||||
},
|
||||
"leverageStorageSnapshots": False,
|
||||
"leverageStorageSnapshotsForHyperFlex": False,
|
||||
"isPaused": False
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CreateAZSQLProtectionJob(self, srcId, pgName):
|
||||
apiCall = "/public/protectionJobs/"
|
||||
payload = {
|
||||
"name": pgName,
|
||||
|
||||
# NDIT - Bronze Policy
|
||||
"policyId": "7780085755317378:1694019083558:289",
|
||||
|
||||
# Default Storage Domain
|
||||
"viewBoxId": 36,
|
||||
|
||||
# SQL Server kRootContainer
|
||||
"parentSourceId": 6,
|
||||
|
||||
"sourceIds": [
|
||||
srcId
|
||||
],
|
||||
|
||||
"startTime": {
|
||||
"hour": 22,
|
||||
"minute": 00
|
||||
},
|
||||
|
||||
"timezone": "America/Chicago",
|
||||
|
||||
"fullProtectionSlaTimeMins": 480,
|
||||
"incrementalProtectionSlaTimeMins": 480,
|
||||
"priority": "kLow",
|
||||
"LeverageSanTransport": False,
|
||||
|
||||
# Default Indexing Policy
|
||||
"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
|
||||
"environment": "kSQL",
|
||||
"environmentParameters": {
|
||||
"sqlParameters": {
|
||||
"userDatabasePreference": "kBackupAllDatabases",
|
||||
"backupSystemDatabases": bool('true'),
|
||||
"aagPreferenceFromSqlServer": bool('true'),
|
||||
"backupType": "kSqlVSSVolume",
|
||||
"backupVolumesOnly": bool('true')
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"abortInBlackoutPeriod": False,
|
||||
"qosType": "kBackupHDD",
|
||||
"description": "",
|
||||
"isPaused": False
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CreateAZProtectionJob(self, pgName, parentId, tagId):
|
||||
base_url = ('https://{}/v2').format(self.server)
|
||||
apiCall = "/data-protect/protection-groups"
|
||||
payload = {
|
||||
"name": pgName,
|
||||
"description": "",
|
||||
# NDIT - Bronze Policy
|
||||
#"policyId": "5033496487614715:1619543500905:271778",
|
||||
"policyId": "7780085755317378:1694019083558:2291",
|
||||
# Default Storage Domain
|
||||
"storageDomainId": 36,
|
||||
"startTime": {
|
||||
"hour": 19,
|
||||
"minute": 00,
|
||||
"timezone": "America/Chicago"
|
||||
},
|
||||
"priority": "kLow",
|
||||
"sla": [
|
||||
{
|
||||
"backupRunType": "kIncremental",
|
||||
"slaMinutes": 480
|
||||
},
|
||||
{
|
||||
"backupRunType": "kFull",
|
||||
"slaMinutes": 480
|
||||
}
|
||||
],
|
||||
"isPaused": True,
|
||||
"environment": "kAzure",
|
||||
"azureParams": {
|
||||
"protectionType": "kNative",
|
||||
"nativeProtectionTypeParams":{
|
||||
"objects": [],
|
||||
"vmTagIds": [
|
||||
[
|
||||
tagId
|
||||
]
|
||||
],
|
||||
"indexingPolicy":{
|
||||
"enableIndexing": True,
|
||||
"includePaths": [
|
||||
"/"
|
||||
],
|
||||
"excludePaths": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
"sourceId": parentId
|
||||
},
|
||||
},
|
||||
"abortInBlackoutPeriod": False
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
print(base_url + apiCall)
|
||||
response = requests.request("POST", base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CreateSQLProtectionJob(self, srcId, pgName):
|
||||
apiCall = "/public/protectionJobs/"
|
||||
payload = {
|
||||
"name": pgName,
|
||||
|
||||
# NDIT - Bronze Policy
|
||||
"policyId": "4847477517838800:1610060943809:6402",
|
||||
|
||||
# Default Storage Domain
|
||||
"viewBoxId": 198,
|
||||
|
||||
# SQL Server kRootContainer
|
||||
"parentSourceId": 8702,
|
||||
|
||||
"sourceIds": [
|
||||
srcId
|
||||
],
|
||||
|
||||
"startTime": {
|
||||
"hour": 22,
|
||||
"minute": 00
|
||||
},
|
||||
|
||||
"timezone": "America/Chicago",
|
||||
|
||||
"fullProtectionSlaTimeMins": 480,
|
||||
"incrementalProtectionSlaTimeMins": 480,
|
||||
"priority": "kLow",
|
||||
"LeverageSanTransport": False,
|
||||
|
||||
# Default Indexing Policy
|
||||
"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
|
||||
"environment": "kSQL",
|
||||
"environmentParameters": {
|
||||
"sqlParameters": {
|
||||
"userDatabasePreference": "kBackupAllDatabases",
|
||||
"backupSystemDatabases": bool('true'),
|
||||
"aagPreferenceFromSqlServer": bool('true'),
|
||||
"backupType": "kSqlVSSVolume",
|
||||
"backupVolumesOnly": bool('true')
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"abortInBlackoutPeriod": False,
|
||||
"qosType": "kBackupHDD",
|
||||
"description": "",
|
||||
"isPaused": False
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
def CreatePhysicalProtectionJob(self, srcId, pgName):
|
||||
apiCall = "/public/protectionJobs/"
|
||||
payload = {
|
||||
"name": pgName,
|
||||
"environment": "kPhysical",
|
||||
|
||||
# NDIT - Bronze Policy
|
||||
"policyId": "4847477517838800:1610060943809:6402",
|
||||
|
||||
# Default Storage Domain
|
||||
"viewBoxId": 198,
|
||||
|
||||
# Physical Servers
|
||||
"parentSourceId": 3149,
|
||||
|
||||
"sourceIds": [
|
||||
srcId
|
||||
],
|
||||
|
||||
"startTime": {
|
||||
"hour": 18,
|
||||
"minute": 00
|
||||
},
|
||||
|
||||
"timezone": "America/Chicago",
|
||||
|
||||
"fullProtectionSlaTimeMins": 480,
|
||||
"incrementalProtectionSlaTimeMins": 480,
|
||||
"priority": "kLow",
|
||||
"LeverageSanTransport": False,
|
||||
|
||||
# Default Indexing Policy
|
||||
"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
|
||||
"abortInBlackoutPeriod": False,
|
||||
"performSourceSideDedup": True,
|
||||
"qosType": "kBackupHDD",
|
||||
"description": "",
|
||||
"isPaused": False
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
|
||||
################ Security Methods #######################
|
||||
|
||||
def UpdatePermissions(self, sourceIds, cohesitySid):
|
||||
apiCall = "/public/principals/protectionSources"
|
||||
payload = {
|
||||
"sourcesForPrincipals": [
|
||||
{
|
||||
"protectionSourceIds": sourceIds,
|
||||
"sid": cohesitySid
|
||||
}
|
||||
]
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
|
||||
return (response)
|
||||
@@ -0,0 +1,502 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import time
|
||||
|
||||
# API Documentation
|
||||
# https://docs.servicenow.com/bundle/sandiego-application-development/page/build/applications/concept/api-rest.html
|
||||
|
||||
class SnowAPI:
|
||||
def __init__(self, snInstance):
|
||||
if not ((os.environ.get('SN_USER')) and (os.environ.get('SN_PASS'))):
|
||||
print("\n*** Environment variables are not set for ServiceNow Authentication ***")
|
||||
exit()
|
||||
|
||||
self.snInstance = snInstance
|
||||
self.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
self.user = os.environ['SN_USER']
|
||||
self.pwd = os.environ['SN_PASS']
|
||||
|
||||
def getGroupID(self, grpName):
|
||||
grpName = grpName.replace(" ","%20")
|
||||
url = 'https://{}/api/now/table/sys_user_group?sysparm_query=name={}'.format(self.snInstance, grpName)
|
||||
apiResponse = requests.get(url, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]['sys_id']
|
||||
|
||||
def getUserID(self, userName):
|
||||
userName = userName.replace(" ","%20")
|
||||
apiCall = 'https://{}/api/now/table/sys_user?sysparm_query=user_name={}'.format(self.snInstance, userName)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]['sys_id']
|
||||
|
||||
def getCMDBItemByIP(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=ip_address={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBItemByHostName(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=host_name={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBItemByFQDN(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=fqdn={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
if len(apiData['result']) > 0:
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
print("Result 2: {0}".format(apiData))
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBAppById(self, sysId):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_service_auto?sys_id={1}".format(self.snInstance, sysId)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]
|
||||
|
||||
def getStandardChangeTemplateID(self, templateName):
|
||||
# Could be improved, might throw unexpected results if the group isn't found
|
||||
# Needs error handling
|
||||
|
||||
allData = []
|
||||
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/template?sysparm_query=active=true".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
|
||||
for i in data['result']:
|
||||
allData.append(i)
|
||||
|
||||
for record in allData:
|
||||
if 'sys_name' in record:
|
||||
if record['sys_name']['display_value'] == templateName:
|
||||
sysID = record['sys_id']['value']
|
||||
break
|
||||
|
||||
return sysID
|
||||
|
||||
def getRequestItemFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_req_item?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['number']
|
||||
|
||||
return item
|
||||
|
||||
def getTaskNumFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['number']
|
||||
|
||||
return item
|
||||
|
||||
def getTaskSysIdFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['sys_id']
|
||||
|
||||
return item
|
||||
|
||||
def getServiceCatalogs(self):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogCategories(self, sys_id):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs/{1}/categories".format(self.snInstance,sys_id)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogItems(self):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogItemByName(self, itemName):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
for item in items['result']:
|
||||
if item['name'] == itemName:
|
||||
return item
|
||||
|
||||
def getSpecificCatalogItem(self, sysId):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items/{1}".format(self.snInstance,sysId)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getDataFromTaskByReqNum(self, sctask, reqNum):
|
||||
# This is only written to handle one task per REQ number!!!
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_fields=state%2Cnumber%2Csys_id%2Cdescription%2Cshort_description%2Cvariables.application_name%2Cvariables.additional_comments%2Cclose_notes%2Cclosed_by&sysparm_limit=1&sysparm_display_value=true".format(self.snInstance, reqNum)
|
||||
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
|
||||
if data['result'][0]['number'] == sctask:
|
||||
items = data['result'][0]
|
||||
else:
|
||||
items = ""
|
||||
|
||||
return items
|
||||
|
||||
def updateTaskDescriptions(self, sysId, short_desc, desc):
|
||||
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
|
||||
|
||||
payload = {
|
||||
'short_description': short_desc,
|
||||
'description': desc
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to update task:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assignTaskToUser(self, sysId, userName):
|
||||
userID = self.getUserID(userName)
|
||||
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
|
||||
payload = {
|
||||
"assigned_to": userID
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to update task:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def postAppServerRequest(self,itemID,requestFor,requestBy,approver,requestType,comments,appName,env):
|
||||
requestForID = self.getUserID(requestFor)
|
||||
requestByID = self.getUserID(requestBy)
|
||||
approvalID = self.getUserID(approver)
|
||||
|
||||
# Statically set these so we always force approvals to go to Computer Systems
|
||||
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
|
||||
departmentCode = "112"
|
||||
divisionID = "f40758231b321450bba0113fad4bcb2d"
|
||||
divisionCode = "32"
|
||||
|
||||
payload = {
|
||||
"get_portal_messages" : "true",
|
||||
"sysparm_quantity" : "1",
|
||||
"sysparm_no_validation" : "true",
|
||||
"variables" : {
|
||||
"v_approval_department" : departmentID,
|
||||
"v_approval_department_code" : departmentCode,
|
||||
"v_approval_division" : divisionID,
|
||||
"v_approval_division_code" : divisionCode,
|
||||
"v_manager" : approvalID,
|
||||
"v_requested_by" : requestByID,
|
||||
"v_requested_for" : requestForID,
|
||||
"request_type" : requestType,
|
||||
"additional_comments" : comments,
|
||||
"application_name" : appName,
|
||||
"environment" : env,
|
||||
"require_hosting_quote" : "No",
|
||||
"add_change_disaster_recovery" : "No"
|
||||
}
|
||||
}
|
||||
|
||||
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
return apiResponse
|
||||
|
||||
def postGenericSerivceRequest(self,itemID,requestFor,requestBy,approver,requestType,comments):
|
||||
requestForID = self.getUserID(requestFor)
|
||||
requestByID = self.getUserID(requestBy)
|
||||
approvalID = self.getUserID(approver)
|
||||
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
|
||||
divisionID = "f40758231b321450bba0113fad4bcb2d"
|
||||
|
||||
payload = {
|
||||
"get_portal_messages" : "true",
|
||||
"sysparm_quantity" : "1",
|
||||
"sysparm_no_validation" : "true",
|
||||
"variables" : {
|
||||
"v_approval_department" : departmentID,
|
||||
"v_approval_division" : divisionID,
|
||||
"v_manager" : approvalID,
|
||||
"v_requested_by" : requestByID,
|
||||
"v_requested_for" : requestForID,
|
||||
"v_type" : requestType,
|
||||
"additional_comments" : comments
|
||||
}
|
||||
}
|
||||
|
||||
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
return apiResponse
|
||||
|
||||
def getGenericData(self, url):
|
||||
apiCall = "https://{0}/".format(self.snInstance,url)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def submitTicket(self, user, subject, content):
|
||||
apiCall = ('https://{}/api/now/table/incident').format(self.snInstance)
|
||||
userID=self.getUserID(user)
|
||||
|
||||
payload = {
|
||||
'caller_id': userID,
|
||||
'short_description': subject,
|
||||
'description': content
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assignTicketToGroup(self, sysId, grpName):
|
||||
grpId = self.getGroupID(grpName)
|
||||
url = 'https://{}/api/now/table/incident/{}'.format(self.snInstance,sysId)
|
||||
|
||||
#Statically set the group to storage, this value was derived by decoding the web URL on storage tickets
|
||||
payload = {
|
||||
'assignment_group': grpId
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(url, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assessNormalChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/normal/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"state": "assess"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def addStandardChangeNotes(self, sysID, txtWorkNotes):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"work_notes": txtWorkNotes
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def scheduleStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"state": "Scheduled"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def implementStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Implement"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def reviewStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Review"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def closeStandardChange(self, sysID, code, notes):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Closed",
|
||||
"close_code": code,
|
||||
"close_notes": notes
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def CreateNormalChange(self, grpName, assignee, coordinator, approver, category, subCategory, shortDesc, desc, justDesc, implmntPlan, riskImpact, backoutPlan, testPlan, sTime, eTime):
|
||||
|
||||
# Creates a normal change request that will require approval.
|
||||
|
||||
grpID = self.getGroupID(grpName)
|
||||
userID = self.getUserID(assignee)
|
||||
chngCoordID = self.getUserID(coordinator)
|
||||
chngMngrID = self.getUserID(approver)
|
||||
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/normal".format(self.snInstance)
|
||||
|
||||
payload = {
|
||||
"category": category,
|
||||
"u_subcategory": subCategory,
|
||||
"u_change_manager": chngMngrID,
|
||||
"assigned_to": userID,
|
||||
"u_change_coordinator": chngCoordID,
|
||||
"assignment_group": grpID,
|
||||
"short_description": shortDesc,
|
||||
"description": desc,
|
||||
"justification": justDesc,
|
||||
"implementation_plan": implmntPlan,
|
||||
"risk_impact_analysis": riskImpact,
|
||||
"backout_plan": backoutPlan,
|
||||
"test_plan": testPlan,
|
||||
"start_date": sTime,
|
||||
"end_date": eTime,
|
||||
"cab_required": False,
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
changeResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
changeData = changeResponse.json()
|
||||
|
||||
return changeData
|
||||
|
||||
def CreateStandardChange(self, templateName, grpName, assignee, coordinator, approver, category, subcategory, txtShortDesc, txtJustification):
|
||||
templateID = self.getStandardChangeTemplateID(templateName)
|
||||
grpID = self.getGroupID(grpName)
|
||||
userID = self.getUserID(assignee)
|
||||
chngCoordID = self.getUserID(coordinator)
|
||||
chngMngrID = self.getUserID(approver)
|
||||
|
||||
sTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
eTime = (datetime.now()+timedelta(minutes=+1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,templateID)
|
||||
|
||||
payload={
|
||||
"category": category,
|
||||
"u_subcategory": subcategory,
|
||||
"u_change_manager": chngMngrID,
|
||||
"assigned_to": userID,
|
||||
"u_change_coordinator": chngCoordID,
|
||||
"assignment_group": grpID,
|
||||
"short_description": txtShortDesc,
|
||||
"justification": txtJustification,
|
||||
"start_date": sTime,
|
||||
"end_date": eTime
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
######################################### END CLASS ################################
|
||||
@@ -0,0 +1,502 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import time
|
||||
|
||||
# API Documentation
|
||||
# https://docs.servicenow.com/bundle/sandiego-application-development/page/build/applications/concept/api-rest.html
|
||||
|
||||
class SnowAPI:
|
||||
def __init__(self, snInstance):
|
||||
if not ((os.environ.get('SN_USER')) and (os.environ.get('SN_PASS'))):
|
||||
print("\n*** Environment variables are not set for ServiceNow Authentication ***")
|
||||
exit()
|
||||
|
||||
self.snInstance = snInstance
|
||||
self.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
self.user = os.environ['SN_USER']
|
||||
self.pwd = os.environ['SN_PASS']
|
||||
|
||||
def getGroupID(self, grpName):
|
||||
grpName = grpName.replace(" ","%20")
|
||||
url = 'https://{}/api/now/table/sys_user_group?sysparm_query=name={}'.format(self.snInstance, grpName)
|
||||
apiResponse = requests.get(url, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]['sys_id']
|
||||
|
||||
def getUserID(self, userName):
|
||||
userName = userName.replace(" ","%20")
|
||||
apiCall = 'https://{}/api/now/table/sys_user?sysparm_query=user_name={}'.format(self.snInstance, userName)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]['sys_id']
|
||||
|
||||
def getCMDBItemByIP(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=ip_address={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBItemByHostName(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=host_name={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBItemByFQDN(self, pattern):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=fqdn={1}&sysparm_limit=10".format(self.snInstance, pattern)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
if len(apiData['result']) > 0:
|
||||
try:
|
||||
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
|
||||
appName = appNameRec['name']
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"link": apiData['result'][0]['u_nd_application_svc']['link'],
|
||||
"value": apiData['result'][0]['u_nd_application_svc']['value'],
|
||||
"name": appName
|
||||
}
|
||||
})
|
||||
except:
|
||||
print("Result 2: {0}".format(apiData))
|
||||
apiData['result'][0].update({
|
||||
"u_nd_application_svc": {
|
||||
"name": "UNDEFINED"
|
||||
},
|
||||
"environment": "UNDEFINED"
|
||||
})
|
||||
return apiData['result']
|
||||
|
||||
def getCMDBAppById(self, sysId):
|
||||
apiCall = "https://{0}/api/now/table/cmdb_ci_service_auto?sys_id={1}".format(self.snInstance, sysId)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
apiData = apiResponse.json()
|
||||
|
||||
return apiData['result'][0]
|
||||
|
||||
def getStandardChangeTemplateID(self, templateName):
|
||||
# Could be improved, might throw unexpected results if the group isn't found
|
||||
# Needs error handling
|
||||
|
||||
allData = []
|
||||
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/template?sysparm_query=active=true".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
|
||||
for i in data['result']:
|
||||
allData.append(i)
|
||||
|
||||
for record in allData:
|
||||
if 'sys_name' in record:
|
||||
if record['sys_name']['display_value'] == templateName:
|
||||
sysID = record['sys_id']['value']
|
||||
break
|
||||
|
||||
return sysID
|
||||
|
||||
def getRequestItemFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_req_item?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['number']
|
||||
|
||||
return item
|
||||
|
||||
def getTaskNumFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['number']
|
||||
|
||||
return item
|
||||
|
||||
def getTaskSysIdFromReqNum(self, reqNum):
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
item = data['result'][0]['sys_id']
|
||||
|
||||
return item
|
||||
|
||||
def getServiceCatalogs(self):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogCategories(self, sys_id):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs/{1}/categories".format(self.snInstance,sys_id)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogItems(self):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getServiceCatalogItemByName(self, itemName):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
for item in items['result']:
|
||||
if item['name'] == itemName:
|
||||
return item
|
||||
|
||||
def getSpecificCatalogItem(self, sysId):
|
||||
apiCall = "https://{0}/api/sn_sc/servicecatalog/items/{1}".format(self.snInstance,sysId)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def getDataFromTaskByReqNum(self, sctask, reqNum):
|
||||
# This is only written to handle one task per REQ number!!!
|
||||
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_fields=state%2Cnumber%2Csys_id%2Cdescription%2Cshort_description%2Cvariables.application_name%2Cvariables.additional_comments%2Cclose_notes%2Cclosed_by&sysparm_limit=1&sysparm_display_value=true".format(self.snInstance, reqNum)
|
||||
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
data = apiResponse.json()
|
||||
|
||||
if data['result'][0]['number'] == sctask:
|
||||
items = data['result'][0]
|
||||
else:
|
||||
items = ""
|
||||
|
||||
return items
|
||||
|
||||
def updateTaskDescriptions(self, sysId, short_desc, desc):
|
||||
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
|
||||
|
||||
payload = {
|
||||
'short_description': short_desc,
|
||||
'description': desc
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to update task:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assignTaskToUser(self, sysId, userName):
|
||||
userID = self.getUserID(userName)
|
||||
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
|
||||
payload = {
|
||||
"assigned_to": userID
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to update task:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def postAppServerRequest(self,itemID,requestFor,requestBy,approver,requestType,comments,appName,env):
|
||||
requestForID = self.getUserID(requestFor)
|
||||
requestByID = self.getUserID(requestBy)
|
||||
approvalID = self.getUserID(approver)
|
||||
|
||||
# Statically set these so we always force approvals to go to Computer Systems
|
||||
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
|
||||
departmentCode = "112"
|
||||
divisionID = "f40758231b321450bba0113fad4bcb2d"
|
||||
divisionCode = "32"
|
||||
|
||||
payload = {
|
||||
"get_portal_messages" : "true",
|
||||
"sysparm_quantity" : "1",
|
||||
"sysparm_no_validation" : "true",
|
||||
"variables" : {
|
||||
"v_approval_department" : departmentID,
|
||||
"v_approval_department_code" : departmentCode,
|
||||
"v_approval_division" : divisionID,
|
||||
"v_approval_division_code" : divisionCode,
|
||||
"v_manager" : approvalID,
|
||||
"v_requested_by" : requestByID,
|
||||
"v_requested_for" : requestForID,
|
||||
"request_type" : requestType,
|
||||
"additional_comments" : comments,
|
||||
"application_name" : appName,
|
||||
"environment" : env,
|
||||
"require_hosting_quote" : "No",
|
||||
"add_change_disaster_recovery" : "No"
|
||||
}
|
||||
}
|
||||
|
||||
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
return apiResponse
|
||||
|
||||
def postGenericSerivceRequest(self,itemID,requestFor,requestBy,approver,requestType,comments):
|
||||
requestForID = self.getUserID(requestFor)
|
||||
requestByID = self.getUserID(requestBy)
|
||||
approvalID = self.getUserID(approver)
|
||||
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
|
||||
divisionID = "f40758231b321450bba0113fad4bcb2d"
|
||||
|
||||
payload = {
|
||||
"get_portal_messages" : "true",
|
||||
"sysparm_quantity" : "1",
|
||||
"sysparm_no_validation" : "true",
|
||||
"variables" : {
|
||||
"v_approval_department" : departmentID,
|
||||
"v_approval_division" : divisionID,
|
||||
"v_manager" : approvalID,
|
||||
"v_requested_by" : requestByID,
|
||||
"v_requested_for" : requestForID,
|
||||
"v_type" : requestType,
|
||||
"additional_comments" : comments
|
||||
}
|
||||
}
|
||||
|
||||
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
return apiResponse
|
||||
|
||||
def getGenericData(self, url):
|
||||
apiCall = "https://{0}/".format(self.snInstance,url)
|
||||
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
|
||||
items = apiResponse.json()
|
||||
|
||||
return items
|
||||
|
||||
def submitTicket(self, user, subject, content):
|
||||
apiCall = ('https://{}/api/now/table/incident').format(self.snInstance)
|
||||
userID=self.getUserID(user)
|
||||
|
||||
payload = {
|
||||
'caller_id': userID,
|
||||
'short_description': subject,
|
||||
'description': content
|
||||
}
|
||||
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assignTicketToGroup(self, sysId, grpName):
|
||||
grpId = self.getGroupID(grpName)
|
||||
url = 'https://{}/api/now/table/incident/{}'.format(self.snInstance,sysId)
|
||||
|
||||
#Statically set the group to storage, this value was derived by decoding the web URL on storage tickets
|
||||
payload = {
|
||||
'assignment_group': grpId
|
||||
}
|
||||
encoded_payload = json.dumps(payload)
|
||||
apiResponse = requests.put(url, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
|
||||
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
|
||||
print('Failed to create ticket:')
|
||||
print('\tStatus:', apiResponse.status_code)
|
||||
print('\tError Response:',apiResponse.json())
|
||||
exit(1)
|
||||
else:
|
||||
return apiResponse.json()
|
||||
|
||||
def assessNormalChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/normal/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"state": "assess"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def addStandardChangeNotes(self, sysID, txtWorkNotes):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"work_notes": txtWorkNotes
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def scheduleStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
|
||||
payload = {
|
||||
"state": "Scheduled"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def implementStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Implement"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def reviewStandardChange(self, sysID):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Review"
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def closeStandardChange(self, sysID, code, notes):
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
|
||||
payload = {
|
||||
"state": "Closed",
|
||||
"close_code": code,
|
||||
"close_notes": notes
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
def CreateNormalChange(self, grpName, assignee, coordinator, approver, category, subCategory, shortDesc, desc, justDesc, implmntPlan, riskImpact, backoutPlan, testPlan, sTime, eTime):
|
||||
|
||||
# Creates a normal change request that will require approval.
|
||||
|
||||
grpID = self.getGroupID(grpName)
|
||||
userID = self.getUserID(assignee)
|
||||
chngCoordID = self.getUserID(coordinator)
|
||||
chngMngrID = self.getUserID(approver)
|
||||
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/normal".format(self.snInstance)
|
||||
|
||||
payload = {
|
||||
"category": category,
|
||||
"u_subcategory": subCategory,
|
||||
"u_change_manager": chngMngrID,
|
||||
"assigned_to": userID,
|
||||
"u_change_coordinator": chngCoordID,
|
||||
"assignment_group": grpID,
|
||||
"short_description": shortDesc,
|
||||
"description": desc,
|
||||
"justification": justDesc,
|
||||
"implementation_plan": implmntPlan,
|
||||
"risk_impact_analysis": riskImpact,
|
||||
"backout_plan": backoutPlan,
|
||||
"test_plan": testPlan,
|
||||
"start_date": sTime,
|
||||
"end_date": eTime,
|
||||
"cab_required": False,
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
changeResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
changeData = changeResponse.json()
|
||||
|
||||
return changeData
|
||||
|
||||
def CreateStandardChange(self, templateName, grpName, assignee, coordinator, approver, category, subcategory, txtShortDesc, txtJustification):
|
||||
templateID = self.getStandardChangeTemplateID(templateName)
|
||||
grpID = self.getGroupID(grpName)
|
||||
userID = self.getUserID(assignee)
|
||||
chngCoordID = self.getUserID(coordinator)
|
||||
chngMngrID = self.getUserID(approver)
|
||||
|
||||
sTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
eTime = (datetime.now()+timedelta(minutes=+1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,templateID)
|
||||
|
||||
payload={
|
||||
"category": category,
|
||||
"u_subcategory": subcategory,
|
||||
"u_change_manager": chngMngrID,
|
||||
"assigned_to": userID,
|
||||
"u_change_coordinator": chngCoordID,
|
||||
"assignment_group": grpID,
|
||||
"short_description": txtShortDesc,
|
||||
"justification": txtJustification,
|
||||
"start_date": sTime,
|
||||
"end_date": eTime
|
||||
}
|
||||
encoded_payload=json.dumps(payload)
|
||||
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
|
||||
return apiResponse.json()
|
||||
|
||||
######################################### END CLASS ################################
|
||||
@@ -0,0 +1,175 @@
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
import urllib3
|
||||
import requests
|
||||
from requests_ntlm import HttpNtlmAuth
|
||||
# Use pip install requests_ntlm to install the necessary modules
|
||||
|
||||
class API(object):
|
||||
def __init__(self):
|
||||
if not ((os.environ.get('ITD_SHAREPOINT_PASS')) and (os.environ.get('ITD_SHAREPOINT_USER'))):
|
||||
print("\n*** ERROR: Environment Variables are not set for SharePoint ***")
|
||||
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
|
||||
exit(1)
|
||||
else:
|
||||
# Username is in the format of 'NDGOV\\<userName>'
|
||||
sp_user = os.environ['ITD_SHAREPOINT_USER']
|
||||
sp_pass = os.environ['ITD_SHAREPOINT_PASS']
|
||||
|
||||
self.server_url = "https://share.nd.gov/"
|
||||
self.site_url = self.server_url + "itd/Computer-Systems/Distributed-Systems/VMWare/_api/web/"
|
||||
self.context_url = self.server_url + "itd/Computer-Systems/Distributed-Systems/VMWare/_api/contextinfo/"
|
||||
self.env = "All"
|
||||
|
||||
self.headers = {
|
||||
"Accept":"application/json; odata=verbose",
|
||||
"Content-Type":"application/json; odata=verbose",
|
||||
"odata":"verbose",
|
||||
"X-RequestForceAuthentication":"true"
|
||||
}
|
||||
|
||||
self.auth = HttpNtlmAuth(sp_user, sp_pass)
|
||||
|
||||
def get_AllNodes(self):
|
||||
|
||||
list_name = 'VM Guests'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
list_itemcount = r.json()['d']['ItemCount']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
concat_items = []
|
||||
# Parse the list
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
cur_page = requests.get(api_items_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
concat_items += cur_page.json()['d']['results']
|
||||
|
||||
# Sharepoint uses pagination with '__next' in the JSON results to indicate additional pages of information
|
||||
# are present. Loop through the pages and concatenate the information into one dataset
|
||||
while '__next' in cur_page.json()['d']:
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
cur_page = requests.get(cur_page.json()['d']['__next'], auth=self.auth, headers=self.headers, verify=False)
|
||||
concat_items += cur_page.json()['d']['results']
|
||||
|
||||
return concat_items
|
||||
|
||||
def get_ActiveNodes(self, e):
|
||||
|
||||
self.env = e
|
||||
list_name = 'VM Guests'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
list_itemcount = r.json()['d']['ItemCount']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
if self.env == 'All':
|
||||
active_items_url = api_items_url + "?$filter=(Status eq 'Current') or (Status eq 'Change')"
|
||||
else:
|
||||
active_items_url = api_items_url + "?$filter=(Environment eq '%s') and ((Status eq 'Current') or (Status eq 'Change'))" % self.env
|
||||
concat_items = []
|
||||
|
||||
# Parse the list
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
cur_page = requests.get(active_items_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
concat_items += cur_page.json()['d']['results']
|
||||
|
||||
# Sharepoint uses pagination with '__next' in the JSON results to indicate additional pages of information
|
||||
# are present. Loop through the pages and concatenate the information into one dataset
|
||||
while '__next' in cur_page.json()['d']:
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
cur_page = requests.get(cur_page.json()['d']['__next'], auth=self.auth, headers=self.headers, verify=False)
|
||||
concat_items += cur_page.json()['d']['results']
|
||||
|
||||
return concat_items
|
||||
|
||||
def find_VM(self, hostname):
|
||||
|
||||
domains = [".nd.gov", ".ndtestdev.nd.dev", ".ndcloud.gov", ".testnd.gov", ".itd.nd.gov", ".ns.nd.gov", ".cloudapp.azure.com", ".uat.mmis.nd.gov", ".test.ndcloud.gov", ".k12tst.nd.us", ".legend.nd.gov", ".tst.mmis.dev", ".trn.mmis.nd.gov", ".sit.mmis.nd.gov", ".nd.gov-mag", ".uat.mmis.dev", ".mo.legend.nd.gov", ".video.nd.gov", ".mmis.nd.gov", ".sit.legend.nd.gov", ".trn.mmis.dev", ".prm.mmis.nd.gov", ".state.nd.us", ".k12.nd.us", ".stg.k12.nd.us", ".sit.mmis.dev", "ndnic.com"]
|
||||
|
||||
for domain in domains:
|
||||
try:
|
||||
data = self.get_Node(hostname + domain)
|
||||
if 'Title' in data[0]:
|
||||
hostname = data[0]['Title']
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
return(hostname)
|
||||
|
||||
def get_Node(self, server):
|
||||
|
||||
myNode = server
|
||||
list_name = 'VM Guests'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
singleServer_url = api_items_url + "?$filter=Title eq '%s'" % myNode
|
||||
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
|
||||
return cur_page.json()['d']['results']
|
||||
|
||||
def get_Agency(self, aID):
|
||||
|
||||
agencyID = aID
|
||||
list_name = 'Agency'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
singleServer_url = api_items_url + "?$filter=ID eq '%s'" % agencyID
|
||||
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
|
||||
return cur_page.json()['d']['results'][0]['Title']
|
||||
|
||||
def get_AppName(self, appID):
|
||||
|
||||
applicationID = appID
|
||||
list_name = 'VM App Name'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
singleServer_url = api_items_url + "?$filter=ID eq '%s'" % applicationID
|
||||
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
|
||||
return cur_page.json()['d']['results'][0]['Title']
|
||||
|
||||
def get_AppNodes(self, appID):
|
||||
|
||||
applicationID = appID
|
||||
list_name = 'VM Guests'
|
||||
|
||||
# Get list information
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
|
||||
list_guid = r.json()['d']['Id']
|
||||
|
||||
# Query list items
|
||||
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
|
||||
singleServer_url = api_items_url + "?$filter=AppName eq '%s'" % appID
|
||||
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
|
||||
|
||||
return cur_page.json()['d']['results']
|
||||
@@ -0,0 +1,770 @@
|
||||
#!/usr/bin/python
|
||||
#-------------------------------------------------------------------------------------------------#
|
||||
# Author: Cliff Cogdill
|
||||
# Description: This script accomplishes a few things:
|
||||
# - Reviews all assets in the CMDB to determine if they have been setup in backups.
|
||||
# - If not, identify them and send create a ticket for investigation.
|
||||
# - Reviews paused protection jobs to determine if the job is still empty.
|
||||
# - If not empty, resume the job.
|
||||
# - Send summary report of unprotected servers and VM jobs to the storage team.
|
||||
# ------------------------------------------------------------------------------------------------#
|
||||
|
||||
import sys,argparse,json,time,yaml,re,smtplib
|
||||
from email.message import EmailMessage
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import sharePointAPI as sharePoint
|
||||
import automationsAPI as dashboard
|
||||
import serviceNowAPI as snow
|
||||
#import nditServiceNow as snow
|
||||
import logging
|
||||
#import pymsteams
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
parser.add_argument('--excludeTickets', '-x', action='store_true')
|
||||
parser.add_argument('--refresh', '-r', action='store_true')
|
||||
parser.add_argument('--type', '-t', type=str, action='store')
|
||||
parser.add_argument('--vCenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def SendEmail(body, cluster):
|
||||
|
||||
if debug:
|
||||
recipients = ['itbackups@nd.gov']
|
||||
else:
|
||||
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov','khellman@nd.gov','mlaverdure@nd.gov','ndheck@nd.gov']
|
||||
|
||||
email = EmailMessage()
|
||||
email['Subject'] = "Backup Review for {0}".format(cluster)
|
||||
email['From'] = "No-Reply@nd.gov"
|
||||
email['To'] = ", ".join(recipients)
|
||||
email.set_content(body)
|
||||
|
||||
with smtplib.SMTP('apprelay.nd.gov') as smtp:
|
||||
smtp.send_message(email)
|
||||
|
||||
if args.debug:
|
||||
print("Sent email")
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 protectedItems.py -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
|
||||
print("\t -d debug: execute in debug mode to expand summary and do not open tickets")
|
||||
print("\t -x excludeTickets: run against prod serivce now but do not open tickets or changes")
|
||||
print("\t -r refresh: force source refresh in debug mode")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -t type of protection source")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
#itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit()
|
||||
|
||||
logging.basicConfig(filename="log/" + args.cluster + '.log', filemode='w', format='%(asctime)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG)
|
||||
|
||||
# Define empty lists that will hold JSON dictionaries
|
||||
vmObjects = []
|
||||
protectedObjects = []
|
||||
vCenters = []
|
||||
|
||||
protectedVM = []
|
||||
unprotectedVM = []
|
||||
undocumentedVM = []
|
||||
vmExemptions = []
|
||||
|
||||
populatedJobs = []
|
||||
populatedPausedJobs = []
|
||||
jobExemptions = []
|
||||
emptyJobs = []
|
||||
|
||||
tickets = []
|
||||
|
||||
|
||||
# Default to the main cluster on premise if the cluster argument was not specified
|
||||
if not args.cluster:
|
||||
args.cluster = "itdmdndpc01.nd.gov"
|
||||
args.type = "kVMware"
|
||||
|
||||
if not args.type:
|
||||
args.type = "kVMware"
|
||||
|
||||
if args.cluster == "itdazdpc01.nd.gov":
|
||||
args.type = "kAzure"
|
||||
|
||||
if args.cluster == "itdazdpc02.nd.gov":
|
||||
args.type = "kAzure"
|
||||
|
||||
logging.info("Creating cluster object for {0} with type {1}".format(args.cluster, args.type))
|
||||
|
||||
# Setup the token for cluster API usage
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Lookup the AppNames and vCenter for unprotected VMs
|
||||
logging.info("Creating cmdb object for SharePoint list")
|
||||
#cmdb = sharePoint.API()
|
||||
cmdb = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
# When true, tickets will only be created in ServiceNow dev and JSON output may be expanded to stdout
|
||||
if args.debug:
|
||||
debug = True
|
||||
else:
|
||||
debug = False
|
||||
|
||||
if args.excludeTickets:
|
||||
excludeTickets = True
|
||||
else:
|
||||
excludeTickets = False
|
||||
|
||||
# Create the connection to ServiceNow
|
||||
logging.info("Creating ticketSystem object for ServiceNow")
|
||||
|
||||
if debug:
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
else:
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
if args.type == "kPhysical":
|
||||
physicalJobs = cluster.GetPhysicalJobs()
|
||||
sys.exit("Warning: Type not implmented for this script.")
|
||||
|
||||
elif args.type == "kSQL":
|
||||
sys.exit("Warning: Type not implmented for this script.")
|
||||
|
||||
elif args.type == "kAzure":
|
||||
|
||||
# Get all registered subscription sources so we can use the source ids for other REST calls
|
||||
logging.info("Collecting all registered Azure sources")
|
||||
nodes = cluster.GetAZsources()
|
||||
|
||||
# Iterate through all the Azure subscriptions to create one large dataset for all VMs
|
||||
logging.info("Creating dataset for all VMs in our Azure subscriptions")
|
||||
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
|
||||
for node in nodes:
|
||||
hypervisorId = node['protectionSource']['id']
|
||||
|
||||
#Refresh the sources so we ensure we are getting valid information
|
||||
if debug:
|
||||
print("Debug enabled, not refreshing source use -r option to override")
|
||||
else:
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
if args.refresh:
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
myObjects = cluster.GetAZObjects(hypervisorId)
|
||||
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
|
||||
|
||||
# Combine all VM objects into one dictionary
|
||||
for val in myObjects:
|
||||
vmObjects.append(val)
|
||||
|
||||
# Combine all protected objects into one dictionary
|
||||
for val in myProtectedObjects:
|
||||
protectedObjects.append(val)
|
||||
|
||||
# Get all the native snapshot protection jobs that exist in the cluster
|
||||
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
|
||||
sourceJobs = cluster.GetAZNativeJobs()
|
||||
|
||||
elif args.type == "kVMware":
|
||||
# Get all registered vCenter sources so we can use the source ids for other REST calls
|
||||
logging.info("Collecting all registered vmWare sources")
|
||||
nodes = cluster.GetVMsources()
|
||||
|
||||
# Iterate through all the vCenter servers to create one large dataset for all VMs
|
||||
logging.info("Creating dataset for all VMs in our Azure subscriptions")
|
||||
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
|
||||
for node in nodes:
|
||||
hypervisorId = node['protectionSource']['id']
|
||||
|
||||
#Refresh the sources so we ensure we are getting valid information
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
myObjects = cluster.GetVMObjects(hypervisorId)
|
||||
myProtectedObjects = cluster.GetProtectedVMobjects(hypervisorId)
|
||||
|
||||
# Combine all VM objects into one dictionary
|
||||
for val in myObjects:
|
||||
vmObjects.append(val)
|
||||
|
||||
# Combine all protected objects into one dictionary
|
||||
for val in myProtectedObjects:
|
||||
protectedObjects.append(val)
|
||||
|
||||
# At this point we have two sets of data
|
||||
# 1. All vms objects whether they are protected or not ( vmObjects )
|
||||
# 2. All protected vm objects ( protectedObjects )
|
||||
|
||||
# Get all the protection jobs that exist for vmware in the cluster
|
||||
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
|
||||
sourceJobs = cluster.GetVMjobs()
|
||||
|
||||
if args.type == "kVMware" or args.type == "kAzure":
|
||||
|
||||
############################## BEGIN: IDENTIFY EMPTY & POPULATE PAUSED JOBS #######################################
|
||||
# Loop through the sourceJobs to see if they show up in at least once in the list of protected objects.
|
||||
# if they don't show up, we can assume the job is created but empty, which is usually the case for
|
||||
# placeholder VMs
|
||||
|
||||
# In certain circumstances, jobs can be defined but exempt from normal processing. These jobs are normally used from time to time for POC work and do not necessarily need to be protected, here we will load them into the exempt jobs array from the exceptions.yml file.
|
||||
logging.info("Opening exemptions file: vars/exemptions.yml")
|
||||
with open("./vars/exemptions.yml", "r") as exemptionJobData:
|
||||
try:
|
||||
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
|
||||
except:
|
||||
print("Unable to load exemptions file.")
|
||||
|
||||
exemptJobs = []
|
||||
|
||||
logging.info("Parsing exemptions YAML")
|
||||
for i in exemptionJobData:
|
||||
for e in exemptionJobData[i]['jobs']:
|
||||
#exemptJobs.append(e.lower())
|
||||
exemptJobs.append([exemptionJobData[i]['id'],e.lower()])
|
||||
|
||||
# Loop through all the jobs for
|
||||
logging.info("Iterating through all protection jobs")
|
||||
print("Starting interation of all protection jobs")
|
||||
for sourceJob in sourceJobs:
|
||||
|
||||
#Set default control variables for each job
|
||||
isJobExempt = False
|
||||
isJobEmpty = True
|
||||
|
||||
# The REST GetVMjobs function returns unfiltered jobs, so lets get rid of the deleted ones
|
||||
if 'isDeleted' in sourceJob:
|
||||
if sourceJob['isDeleted'] == True:
|
||||
logging.info("--Job {0} is marked for deletion, skipping review".format(sourceJob['name']))
|
||||
continue
|
||||
|
||||
# Look through the exemptJobs listing
|
||||
for entry in exemptJobs:
|
||||
parent = entry[0]
|
||||
pattern = entry[1]
|
||||
|
||||
if parent == sourceJob['parentSourceId']:
|
||||
if re.search(pattern, sourceJob['name'].lower()):
|
||||
isJobExempt = True
|
||||
break
|
||||
|
||||
if isJobExempt == True:
|
||||
jobExemptions.append(sourceJob['name'])
|
||||
if debug == True:
|
||||
logging.info("--Job {0} has been exempt from backups, skipping review".format(sourceJob['name']))
|
||||
print("Job {0} is exempt from tickets".format(sourceJob['name']))
|
||||
continue
|
||||
|
||||
# The protection jobs only show tags, they do not show VM ojbects; therefore, to determine if the job is still valid, we have to iterate through all protected objects to see if the job name shows up or not. If it does not show up, then it is assumed to be empty and paused to prevent backup errors. Also, there is usually only one job listed per protected VM; however, there could be cases where a VM is in two or more jobs so we need to check each job listed.
|
||||
# Sample dataset can be seen by using the following url:
|
||||
# https://itdmdndpc01.nd.gov/irisservices/api/v1/public/protectionSources/protectedObjects?environment=kVMware&id=3156
|
||||
logging.info("--Iterating through all protected objects to verify if job {0} is empty".format(sourceJob['name']))
|
||||
for item in protectedObjects:
|
||||
for pJob in item['protectionJobs']:
|
||||
if pJob['name'] == sourceJob['name']:
|
||||
isJobEmpty = False
|
||||
continue
|
||||
|
||||
# Seperate empty from non-empty jobs
|
||||
if isJobEmpty:
|
||||
logging.info("--{0} is empty, pausing future runs".format(sourceJob['name']))
|
||||
emptyJobs.append(sourceJob)
|
||||
# Pause the job so we don't generate erroneous errors.
|
||||
cluster.PauseJob(sourceJob['id'])
|
||||
|
||||
else:
|
||||
# The job is not empty, lets check to see if it is paused so we can create an alert.
|
||||
|
||||
# First we have to check if the isPaused key exists, if it has never been set, it will not.
|
||||
if 'isPaused' in sourceJob:
|
||||
# Now we check to see if isPaused is set to True
|
||||
if sourceJob['isPaused']:
|
||||
logging.info("----{0} is paused but contains VM objects, adding to populatedPausedJobs dataset".format(sourceJob['name']))
|
||||
populatedPausedJobs.append(sourceJob)
|
||||
# isPaused might be defined but it is set to False
|
||||
else:
|
||||
populatedJobs.append(sourceJob)
|
||||
|
||||
# If the job has never been paused, isPaused may not be defined
|
||||
else:
|
||||
populatedJobs.append(sourceJob)
|
||||
|
||||
############################## END: IDENTIFY EMPTY & POPULATE JOBS #######################################
|
||||
logging.info("Iteration of all protection jobs is complete")
|
||||
print("Iteration of all protection jobs is complete")
|
||||
######################### BEGIN: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
|
||||
|
||||
# This block of code is duplicate logic from above, the populatedPausedJobs array should not contain exempt jobs, we already checked for that. - CC 20230928
|
||||
|
||||
# if debug:
|
||||
# print("Exempt jobs:\n{0}".format(exemptJobs))
|
||||
#
|
||||
# for job in populatedPausedJobs:
|
||||
# isJobExempt = False
|
||||
#
|
||||
# for entry in exemptJobs:
|
||||
# parent = entry[0]
|
||||
# pattern = entry[1]
|
||||
#
|
||||
# if parent == job['parentSourceId']:
|
||||
# if re.search(pattern, job['name'].lower()):
|
||||
# isJobExempt = True
|
||||
# break
|
||||
#
|
||||
# if isJobExempt:
|
||||
# continue
|
||||
# else:
|
||||
|
||||
logging.info("Opening tickets for paused jobs that contain VM objects")
|
||||
for job in populatedPausedJobs:
|
||||
if not excludeTickets:
|
||||
subject = "Backup job " + job['name'] + " is paused"
|
||||
message = "This backup job contains VMs and should not be paused per SLA. Either fix the issue and resume the job, exempt the VM(s) from backups, or decommission the VMs."
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
logging.info("Ticket {0} created for {1}".format(incident['result']['number'],job['name']))
|
||||
tickets.append("Ticket {0} created for {1}".format(incident['result']['number'],job['name']))
|
||||
else:
|
||||
jobExemptions.append(job['name'])
|
||||
######################### END: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
|
||||
|
||||
########################### END: JOB CENTRIC WORK #########################
|
||||
############################## BEGIN: IDENTIFY UNPROTECTED VMS #################################
|
||||
# RFE: 002
|
||||
# Open the list of documented exceptions, in the long term this may be in the ServiceNow CMDB
|
||||
# if it is, we would have to look it up using the SNOW API
|
||||
with open("./vars/exemptions.yml", "r") as exemptionData:
|
||||
try:
|
||||
exemptionData = yaml.load(exemptionData, Loader=yaml.FullLoader)
|
||||
except:
|
||||
print("Unable to load exemptions file.")
|
||||
|
||||
exemptions = []
|
||||
|
||||
for i in exemptionData:
|
||||
for e in exemptionData[i]['vms']:
|
||||
#exemptions.append([exemptionData[i]['id'],e.lower()])
|
||||
exemptions.append([exemptionData[i]['id'],e])
|
||||
|
||||
# Find protected / unprotected vms
|
||||
logging.info("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
|
||||
print("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
|
||||
for vm in vmObjects:
|
||||
# We don't need to monitor VMs that are marked as a template VM
|
||||
if vm['environment'] == "kVMware":
|
||||
if 'isVmTemplate' in vm['vmWareProtectionSource']:
|
||||
if vm['vmWareProtectionSource']['isVmTemplate'] == True:
|
||||
continue
|
||||
|
||||
# Assume all VM objects are not protected until we prove otherwise
|
||||
isProtected = False
|
||||
|
||||
# Use flag to determine if VM was found or not
|
||||
for pVM in protectedObjects:
|
||||
if vm['name'] == pVM['protectionSource']['name']:
|
||||
isProtected = True
|
||||
protectedVM.append(vm)
|
||||
break
|
||||
|
||||
# The Azure naming convention is different from the vmware naming convention, we need to normalize the names for sharepoint loookups
|
||||
# and exemption lookups
|
||||
|
||||
# 4/22/2025
|
||||
# Do we have to lookup the names to quantify the domain anymore?
|
||||
# Will need a sample dataset from Cohesity call and SNOW lookups to determine.
|
||||
if vm['environment'] == "kAzure":
|
||||
|
||||
# Azure VMs usually follow the naming convetion of vm-myserver.domain.com
|
||||
# so we split on '-' and use the second part.
|
||||
if "-" in vm['name']:
|
||||
nameParts = vm['name'].split("-")
|
||||
|
||||
# SharePoint
|
||||
#hostname= cmdb.find_VM(nameParts[1])
|
||||
|
||||
# ServiceNow
|
||||
try:
|
||||
cmdbData=cmdb.getCMDBItemByHostName(nameParts[1])
|
||||
cmdbData=cmdbData[0]
|
||||
except:
|
||||
try:
|
||||
print("INFO: possible name mismatch, searching via full name")
|
||||
cmdbData = cmdb.getCMDBItemByHostName(vm['name'])
|
||||
cmdbData=cmdbData[0]
|
||||
except:
|
||||
print(vm['name'] + " not found")
|
||||
undocumentedVM.append(vm['name'])
|
||||
continue
|
||||
|
||||
#if nameParts[1] == hostname:
|
||||
if nameParts[1] != cmdbData['host_name']:
|
||||
|
||||
print("INFO: possible name mismatch, searching via full name")
|
||||
|
||||
# Data wasn't found, search for the full vm name
|
||||
# SharePoint
|
||||
#hostname = cmdb.find_VM(vm['name'])
|
||||
|
||||
# ServiceNow
|
||||
cmdbData = cmdb.getCMDBItemByHostName(vm['name'])
|
||||
cmdbData=cmdbData[0]
|
||||
|
||||
# 2024-04-23: This double lookup was created to handle an edge case where there was a
|
||||
# '-' in the hostname. The VM has since been decomissioned but we will
|
||||
# leave the logic in place but 'continue' the loop and write out a warning.
|
||||
#if cmdbData['host_name'] == "vm-lcsdev1-1":
|
||||
# # Sharepoint
|
||||
# #hostname = cmdb.get_Node("vm-lcsdev1dev.centralus.cloudapp.azure.com")
|
||||
|
||||
# # ServiceNow
|
||||
# cmdbData = cmdb.getCMDBItemByFQDN('vm-lcsdev1dev.centralus.cloudapp.azure.com')
|
||||
# cmdbData=cmdbData[0]
|
||||
|
||||
else:
|
||||
# SharePoint
|
||||
# hostname = cmdb.find_VM(vm['name'])
|
||||
|
||||
# ServiceNow
|
||||
try:
|
||||
cmdbData = cmdb.getCMDBItemByFQDN(vm['name'])
|
||||
cmdbData=cmdbData[0]
|
||||
except:
|
||||
print(vm['name'] + " not found")
|
||||
undocumentedVM.append(vm['name'])
|
||||
continue
|
||||
|
||||
vm.update({"name": cmdbData['fqdn']})
|
||||
|
||||
# This part needs some significant improvement on handling the exceptions. Ideally we would want to ensure the
|
||||
# hostname that is excluded belongs to the same parent in the exemption file and in Cohesity.
|
||||
if not isProtected:
|
||||
isExempted = False
|
||||
for entry in exemptions:
|
||||
parent = entry[0]
|
||||
pattern = entry[1]
|
||||
if parent == vm['parentId']:
|
||||
if re.search(pattern, vm['name'].lower()):
|
||||
isExempted = True
|
||||
vmExemptions.append(vm['name'])
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if isExempted == False:
|
||||
unprotectedVM.append(vm)
|
||||
|
||||
############################## END: IDENTIFY UNPROTECTED VMS #################################
|
||||
|
||||
###################### BEGIN: OPEN TICKETS FOR UNPROTECTED & UNEXEMPTED VMS ########################
|
||||
|
||||
# Setup an array to hold unique AppNames
|
||||
unProtectedApps = []
|
||||
for vm in unprotectedVM:
|
||||
|
||||
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(vm['parentId']))
|
||||
vcenterName = vcenterSource[0]['protectionSource']['name'].split(".")[0]
|
||||
|
||||
if args.type == "kAzure":
|
||||
if vcenterName == "437b2bfa-850e-4464-b6c2-38a68cda7c69":
|
||||
vcenterName = "prd01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-PRD":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "76297098-764c-43de-8525-c9fda1b237be":
|
||||
vcenterName = "npd01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NPD":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "e53aa0c7-824d-40a2-b420-4ab77b1051d2":
|
||||
vcenterName = "infra01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-INFRA")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-INFRA":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "cb39dbef-0ecf-434d-a8b9-444cef8f390a":
|
||||
vcenterName = "cares01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-CARES")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-CARES":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "54048952-c2b2-4c65-ab44-157750caa8e6":
|
||||
vcenterName = "NDIT-PaaS"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NDIT-PaaS")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NDIT-PaaS":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
# Trim off .nd.gov from the name
|
||||
vcenterName = vcenterName.split(".")[0]
|
||||
|
||||
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
|
||||
# SharePoint
|
||||
#cmdbRecord = cmdb.get_Node(vm['name'])
|
||||
|
||||
# Commenting this out & setting it to null because the SeriveNow logic already exists below
|
||||
# ServiceNow
|
||||
try:
|
||||
cmdbRecord = cmdb.getCMDBItemByFQDN(vm['name'])
|
||||
cmdbRecord = cmdbRecord[0]
|
||||
inSnow = True
|
||||
except:
|
||||
inSnow = False
|
||||
|
||||
if not inSnow:
|
||||
|
||||
# 2024-04-23: Sharepoint is gone, the primary source is ServiceNow & the code has been
|
||||
# adjusted accordingly
|
||||
#if len(cmdbRecord) == 0:
|
||||
# Handle cases where the record doesn't exist in Sharepoint yet, but might be going through
|
||||
# the build process in ServiceNow
|
||||
|
||||
|
||||
#try:
|
||||
# print("Trying to find: {}".format(vm['name']))
|
||||
# cmdbData = ticketSystem.getCMDBItemByFQDN(vm['name'])
|
||||
# print("ServiceNow: {0}, vmware: {1}".format(cmdbData[0]['fqdn'],vm['name']))
|
||||
# if cmdbData[0]['fqdn'] == vm['name']:
|
||||
# print("Found in Snow CMDB")
|
||||
# inSnow = True
|
||||
#except:
|
||||
# inSnow = False
|
||||
|
||||
# Part of old logic
|
||||
#if not excludeTickets and (inSnow == False):
|
||||
|
||||
# Open a ticket for unprotected VMs that are not in the CMDB
|
||||
if not excludeTickets:
|
||||
undocumentedVM.append(vm['name'] + " on " + vcenterName)
|
||||
|
||||
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
|
||||
#2024-04-23: Resume tickets for undocumented VMs
|
||||
subject = "Backup job for " + vm['name'] + " on " + vcenterName + " was not found."
|
||||
message = "This VM was not found in the CMDB and is not being backed up.\nInvestigate why the object is not documented in the vmware list, and not being backed up. If necessary have the backup admins exclude the item in the exemptions.yml file"
|
||||
|
||||
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
tickets.append("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
# There is a record for the VM in the CMDB so lets construct the appropriate protection group name
|
||||
else:
|
||||
|
||||
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
|
||||
# Sharepoint
|
||||
#vmAppName = cmdb.get_AppName(cmdbRecord[0]['AppNameId'])
|
||||
#vmEnvironment = cmdbRecord[0]['Environment']
|
||||
|
||||
# ServiceNow
|
||||
vmAppName = cmdbRecord['u_nd_application_svc']['name']
|
||||
vmEnvironment = cmdbRecord['environment']
|
||||
|
||||
if args.type == "kVMware":
|
||||
if vmEnvironment == "Production":
|
||||
jobName = vmAppName + "@" + "PRD-" + vcenterName
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-PRD":
|
||||
jobPolicy=pol['id']
|
||||
#jobPolicy="4847477517838800:1610060943809:72255100"
|
||||
|
||||
elif vmEnvironment == "Test":
|
||||
jobName = vmAppName + "@" + "NPD-" + vcenterName
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NPD":
|
||||
jobPolicy=pol['id']
|
||||
#jobPolicy="4847477517838800:1610060943809:72255060"
|
||||
elif args.type == "kAzure":
|
||||
jobName = "{0}@{1}".format(vmAppName, vcenterName)
|
||||
|
||||
# Don't add the job name to the unprotectedApps array more than once
|
||||
if jobName in unProtectedApps:
|
||||
continue
|
||||
else:
|
||||
# Verify that the job does not already exist in Cohesity
|
||||
verify = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + jobName )
|
||||
|
||||
if len(verify) > 0:
|
||||
for record in verify:
|
||||
if record['name'] != jobName:
|
||||
jobExists = False
|
||||
else:
|
||||
jobExists = True
|
||||
break
|
||||
|
||||
if (len(verify) == 0) or (jobExists == False):
|
||||
unProtectedApps.append(jobName)
|
||||
|
||||
if args.type == "kVMware":
|
||||
tagId = cluster.GetVcenterTagId(vcenterSource, vmAppName)
|
||||
|
||||
if vmEnvironment == "Test":
|
||||
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Production")
|
||||
|
||||
elif vmEnvironment == "Production":
|
||||
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Test")
|
||||
|
||||
if tagId is not None:
|
||||
confirm = "yes"
|
||||
#confirm = input("About to create a protection job for {0}. Are you sure? [yes|no]".format(jobName))
|
||||
if (confirm.lower() == "yes") and (not excludeTickets):
|
||||
print("Creating standard change request for {0}.".format(jobName))
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platform - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
resp = cluster.CreateVMProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId, excludeTag,jobPolicy)
|
||||
postChangeNotes = resp.text
|
||||
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
print("Job {} created, {} complete".format(jobName, changeID['result']['number']['value']))
|
||||
tickets.append("Change {} created for {}".format(changeID['result']['number']['value'],jobName))
|
||||
|
||||
if args.type == "kAzure":
|
||||
print("Create backup job for job {0}".format(jobName))
|
||||
print("Looking up Tag ID for {0}".format(vmAppName))
|
||||
tagId = cluster.GetAzureTagId(vcenterSource, vmAppName)
|
||||
|
||||
if (tagId is not None) and (not excludeTickets):
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
resp = cluster.CreateAZProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId)
|
||||
postChangeNotes = resp.text
|
||||
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
|
||||
print("Created {0} for {1}".format(changeID,jobName))
|
||||
tickets.append("Ticket {0} created for {1}".format(changeID['result']['number']['value'],jobName))
|
||||
|
||||
else:
|
||||
print("Tag not found in Azure: {}".format(vmAppName))
|
||||
subject = "Tag {} was not found in the {} virtualization platform".format(vmAppName, args.type)
|
||||
message = "The application name tag {0} used to create backup job {1} was not found. The ApplicationName tag for {2} must be a case sensitive match with the Sharepoint record.".format(vmAppName, jobName, vm['name'])
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
tickets.append("Ticket {} created for {}".format(incident['result']['number'],vmAppName))
|
||||
|
||||
else:
|
||||
if not excludeTickets:
|
||||
subject = "Backup job for " + vm['name'] + " was not found in " + jobName + "."
|
||||
message = "Assign ticket to vmWare or Storage to investigate vm tagging issues."
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
tickets.append("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
undocumentedVM.append(vm['name'])
|
||||
|
||||
#itdTeamsMessage.text("Empty Jobs: {0}\nPopulated Jobs: {1}\nPopulated Paused Jobs: {2}\nUnprotected VMs: {3}".format(str(len(emptyJobs)),str(len(populatedJobs)),str(len(populatedPausedJobs)),str(len(unprotectedVM))))
|
||||
#itdTeamsMessage.send()
|
||||
body = ""
|
||||
|
||||
body = body + "Job Summary:"
|
||||
data = str(len(populatedJobs)) if len(populatedJobs) > 0 else str(0)
|
||||
body = body + "\n\tPopulated Jobs: " + data
|
||||
|
||||
data = str(len(populatedPausedJobs)) if len(populatedPausedJobs) > 0 else str(0)
|
||||
body = body + "\n\tPaused Jobs: " + data
|
||||
|
||||
data = str(len(emptyJobs)) if len(emptyJobs) > 0 else str(0)
|
||||
body = body + "\n\tEmpty Jobs: " + data
|
||||
|
||||
data = str(len(unProtectedApps)) if len(unProtectedApps) > 0 else str(0)
|
||||
body = body + "\n\tUnprotected Apps: " + data
|
||||
|
||||
body = body + "\n\nVM Summary:"
|
||||
data = str(len(unprotectedVM)) if len(unprotectedVM) > 0 else str(0)
|
||||
body = body + "\n\tUnprotected VMs: " + data
|
||||
|
||||
data = str(len(undocumentedVM)) if len(undocumentedVM) > 0 else str(0)
|
||||
body = body + "\n\tUndocumented VMs: " + data
|
||||
|
||||
body = body + "\n\nTickets Created:"
|
||||
for i in tickets:
|
||||
body = body + "\n\t" + str(i)
|
||||
|
||||
body = body + "\n\nDetails:"
|
||||
body = body + "\n\tEmpty Jobs: "
|
||||
|
||||
jobs=[]
|
||||
for i in emptyJobs:
|
||||
jobs.append(i['name'])
|
||||
jobs.sort()
|
||||
for i in jobs:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
jobs=[]
|
||||
body = body + "\n\tPaused Jobs:"
|
||||
for i in populatedPausedJobs:
|
||||
jobs.append(i['name'])
|
||||
|
||||
jobs.sort()
|
||||
for i in jobs:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
body = body + "\n\tUnprotected Apps:"
|
||||
unProtectedApps.sort()
|
||||
for i in unProtectedApps:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
# The unprotededVM array is a dict of vm objects, we only need the vm name here
|
||||
vms = []
|
||||
body = body + "\n\tUnprotected VMs: "
|
||||
for i in unprotectedVM:
|
||||
vms.append(i['name'])
|
||||
|
||||
vms.sort()
|
||||
for i in vms:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
# End sorting of unprotectedVM
|
||||
|
||||
undocumentedVM.sort()
|
||||
body = body + "\n\tUndocumented Deployed VMs:"
|
||||
for i in undocumentedVM:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
body = body + "\n\tDeployed Backup Exempt VMs:"
|
||||
vmExemptions.sort()
|
||||
for i in vmExemptions:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
body = body + "\n\tDeployed Backup Exempt Apps:"
|
||||
jobExemptions.sort()
|
||||
for i in jobExemptions:
|
||||
body = body + "\n\t\t" + str(i)
|
||||
|
||||
SendEmail(body, args.cluster)
|
||||
|
||||
if debug:
|
||||
print(body)
|
||||
else:
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-dailyProtectionReview.py', 'Units': 120})
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/python
|
||||
#-------------------------------------------------------------------------------------------------------------#
|
||||
# Author: Cliff Cogdill
|
||||
# Description: Gather the protection runs from the last 24 hours, limited to 5000 jobs, and seperate them into
|
||||
# Running, Canceled, Failures, warning, hold, and missed.
|
||||
# If the protection group is empty pause it.
|
||||
# If there are failures in the protection job, open a ServiceNow ticket.
|
||||
# Send out a summary of the previous 24 hour run
|
||||
#
|
||||
#
|
||||
#-------------------------------------------------------------------------------------------------------------#
|
||||
import sys,argparse,json,time,smtplib
|
||||
from email.message import EmailMessage
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--job', '-j', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
parser.add_argument('--debugMode', '-d', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 dailyErrors.py -c cluster1.domain.tld" )
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
def SendEmail(body, cluster):
|
||||
if debugMode:
|
||||
recipients = ['cecogdill@nd.gov']
|
||||
else:
|
||||
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov']
|
||||
|
||||
email = EmailMessage()
|
||||
email['Subject'] = "Cohesity Job Status for {0}".format(cluster)
|
||||
email['From'] = "No-Reply@nd.gov"
|
||||
email['To'] = ", ".join(recipients)
|
||||
email.set_content(body)
|
||||
|
||||
with smtplib.SMTP('apprelay.nd.gov') as smtp:
|
||||
smtp.send_message(email)
|
||||
|
||||
print("Sent email")
|
||||
|
||||
# Define variables
|
||||
args = GetArgs()
|
||||
|
||||
if args.debugMode:
|
||||
debugMode = True
|
||||
else:
|
||||
debugMode = False
|
||||
|
||||
# Check for arguments and act accoringly
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Establish a connection to ServiceNow
|
||||
if debugMode:
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
else:
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
# Establish a connection to Cohesity
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Get yesterday's start time in unixEpoch:
|
||||
prevWindow = cluster.GetRelativeTimestamp(-1, 17, 0, 0)
|
||||
|
||||
# Debug Output
|
||||
if args.debugMode:
|
||||
print("\nPulling backup jobs from {}\n".format(prevWindow))
|
||||
|
||||
# Pull a list of protection runs from yesterday's backup window.
|
||||
jobStatus = cluster.GetFilteredRequest("/public/protectionRuns","?startTimeUsecs=" + str(prevWindow) + "&numRuns=5000")
|
||||
|
||||
#Define the arrays we will use to sort the protectionRuns
|
||||
running = []
|
||||
cancel = []
|
||||
failures = []
|
||||
failControl = []
|
||||
warnings = []
|
||||
holds = []
|
||||
missed = []
|
||||
tickets = []
|
||||
|
||||
# Examine and sort the jobs by type status
|
||||
for job in jobStatus:
|
||||
|
||||
# Debug Output
|
||||
if args.debugMode:
|
||||
print("Status of {0}: {1}\n".format(job['jobName'],job['backupRun']['status']))
|
||||
|
||||
# Look for failures
|
||||
if job['backupRun']['status'] == "kRunning":
|
||||
running.append(job)
|
||||
elif job['backupRun']['status'] == "kCanceling" or job['backupRun']['status'] == "kCanceled":
|
||||
cancel.append(job)
|
||||
elif job['backupRun']['status'] == "kFailure":
|
||||
if job['jobName'] + job['backupRun']['error'] in failControl:
|
||||
continue
|
||||
else:
|
||||
failControl.append(job['jobName'] + job['backupRun']['error'])
|
||||
failures.append(job)
|
||||
elif job['backupRun']['status'] == "kWarning":
|
||||
warnings.append(job)
|
||||
elif job['backupRun']['status'] == "kOnHold":
|
||||
holds.append(job)
|
||||
elif job['backupRun']['status'] == "kMissed":
|
||||
missed.append(job)
|
||||
else:
|
||||
continue
|
||||
|
||||
# Start formatting the email output into the 'body' variable.
|
||||
body = "Running: " + str(len(running))
|
||||
for entry in running:
|
||||
message = "\n\t " + entry['jobName']
|
||||
body = body + message
|
||||
|
||||
body = body + "\nCanceled: " + str(len(cancel))
|
||||
for entry in cancel:
|
||||
message = "\n\t " + entry['jobName']
|
||||
body = body + message
|
||||
|
||||
body = body + "\nFailures: " + str(len(failures))
|
||||
for entry in failures:
|
||||
retired = False
|
||||
|
||||
# Create the message entry
|
||||
message = "\n\t " + entry['jobName']
|
||||
|
||||
if entry['jobName'] == 'NDPERS-Applications@physical':
|
||||
continue
|
||||
|
||||
if 'error' in entry['backupRun']:
|
||||
errorMessage = entry['backupRun']['error']
|
||||
|
||||
# We know there are empty protection jobs but we don't need to create a ticket on them; instead lets pause them and then
|
||||
# add logic to the daily backup audit script (dailyProtectionReview.py) to check the paused jobs for VMs and take action
|
||||
# if needed
|
||||
if errorMessage == "Cannot find any eligible backup source for this run":
|
||||
errorMessage = "Protection group was empty, future runs will be paused by the dailyProtectionReview.py script"
|
||||
#No need to continue and create a ticket so lets go to the next item
|
||||
continue
|
||||
|
||||
for source in entry['backupRun']['sourceBackupStatus']:
|
||||
|
||||
if 'error' in source:
|
||||
#errorMessage = "Details:"
|
||||
|
||||
# Clean up Cohesitys error a bit
|
||||
if "Exceeded the maximum number of permitted snapshots" in source['error']:
|
||||
errorMessage = "\n\t\t" + errorMessage + source['source']['name'] + ":\n\t\t\tAn error occurred while saving \
|
||||
the snapshot: Exceeded the maximum number of permitted snapshots. Check whether or not snapshots on \
|
||||
these objects are allowed in the VM properties. If snapshots are not allowed, ensure the server is \
|
||||
exempted from backups according to NDIT Backup Exemption procedures.\n"
|
||||
else:
|
||||
errorMessage = "\n\t\t" + source['source']['name'] + ":\n\t\t\t" + source['error']
|
||||
|
||||
|
||||
# Retired in ServiceNow
|
||||
this_vm = source['source']['name']
|
||||
cmdbRecord = ticketSystem.getCMDBItemByFQDN(this_vm)
|
||||
|
||||
# ServiceNow returns the index as string object so we either have to convert it to int or the check to str
|
||||
if len(cmdbRecord) > 0:
|
||||
if int(cmdbRecord[0]['operational_status']) == 6:
|
||||
retired = True
|
||||
|
||||
# Create the incident based off the error message in the job
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", "Backup Error for: " + entry['jobName'], errorMessage)
|
||||
incidentID = incident['result']['sys_id']
|
||||
tickets.append(incident['result']['number'])
|
||||
|
||||
if '@SQL' in entry['jobName'] and retired != True:
|
||||
# Assign the incident to storage
|
||||
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Database')
|
||||
|
||||
else:
|
||||
# Assign the incident to storage
|
||||
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Computer Systems Storage')
|
||||
|
||||
message = message + errorMessage
|
||||
body = body + message
|
||||
|
||||
body = body + "\nWarning: " + str(len(warnings))
|
||||
for entry in warnings:
|
||||
message = "\n\t " + entry['jobName']
|
||||
body = body + message
|
||||
|
||||
body = body + "\nHold: " + str(len(holds))
|
||||
for entry in holds:
|
||||
message = "\n\t " + entry['jobName']
|
||||
body = body + message
|
||||
|
||||
body = body + "\nMissed: " + str(len(missed))
|
||||
for entry in missed:
|
||||
message = "\n\t " + entry['jobName']
|
||||
body = body + message
|
||||
|
||||
body = body + "\nTickets: " + str(len(tickets)) + "\n\t Instance: " + ticketSystem.snInstance
|
||||
for t in tickets:
|
||||
message = "\n\t\t" + t
|
||||
body = body + message
|
||||
|
||||
# Send the email to the list of recipients in the local SendEmail function
|
||||
SendEmail(body, args.cluster)
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-dailyErrors.py', 'Units': 60})
|
||||
@@ -0,0 +1,646 @@
|
||||
#!/usr/bin/python
|
||||
#-------------------------------------------------------------------------------------------------#
|
||||
# Author: Cliff Cogdill
|
||||
# Description: This script accomplishes a few things:
|
||||
# - Reviews all assets in the CMDB to determine if they have been setup in backups.
|
||||
# - If not, identify them and send create a ticket for investigation.
|
||||
# - Reviews paused protection jobs to determine if the job is still empty.
|
||||
# - If not empty, resume the job.
|
||||
# - Send summary report of unprotected servers and VM jobs to the storage team.
|
||||
# ------------------------------------------------------------------------------------------------#
|
||||
|
||||
import sys,argparse,json,time,yaml,re,smtplib
|
||||
from email.message import EmailMessage
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import sharePointAPI as sharePoint
|
||||
import automationsAPI as dashboard
|
||||
#import serviceNowAPI as snow
|
||||
import nditServiceNow as snow
|
||||
import logging
|
||||
#import pymsteams
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
parser.add_argument('--excludeTickets', '-x', action='store_true')
|
||||
parser.add_argument('--refresh', '-r', action='store_true')
|
||||
parser.add_argument('--type', '-t', type=str, action='store')
|
||||
parser.add_argument('--vCenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def SendEmail(body, cluster):
|
||||
if debug:
|
||||
recipients = ['itbackups@nd.gov']
|
||||
else:
|
||||
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov','khellman@nd.gov','mlaverdure@nd.gov','ndheck@nd.gov']
|
||||
|
||||
email = EmailMessage()
|
||||
email['Subject'] = "Backup Review for {0}".format(cluster)
|
||||
email['From'] = "No-Reply@nd.gov"
|
||||
email['To'] = ", ".join(recipients)
|
||||
email.set_content(body)
|
||||
|
||||
with smtplib.SMTP('apprelay.nd.gov') as smtp:
|
||||
smtp.send_message(email)
|
||||
|
||||
print("Sent email")
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 protectedItems.py -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
|
||||
print("\t -d debug: execute in debug mode to expand summary and do not open tickets")
|
||||
print("\t -x excludeTickets: run against prod serivce now but do not open tickets or changes")
|
||||
print("\t -r refresh: force source refresh in debug mode")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -t type of protection source")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
#itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit()
|
||||
|
||||
logging.basicConfig(filename="log/" + args.cluster + '.log', filemode='w', format='%(asctime)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG)
|
||||
|
||||
# Define empty lists that will hold JSON dictionaries
|
||||
vmObjects = []
|
||||
protectedObjects = []
|
||||
vCenters = []
|
||||
protectedVM = []
|
||||
unprotectedVM = []
|
||||
undocumentedVM = []
|
||||
populatedJobs = []
|
||||
populatedPausedJobs = []
|
||||
emptyJobs = []
|
||||
|
||||
# Default to the main cluster on premise if the cluster argument was not specified
|
||||
if not args.cluster:
|
||||
args.cluster = "itdmdndpc01.nd.gov"
|
||||
args.type = "kVMware"
|
||||
|
||||
if not args.type:
|
||||
args.type = "kVMware"
|
||||
|
||||
if args.cluster == "itdazdpc01.nd.gov":
|
||||
args.type = "kAzure"
|
||||
|
||||
if args.cluster == "itdazdpc02.nd.gov":
|
||||
args.type = "kAzure"
|
||||
|
||||
logging.info("Creating cluster object for {0} with type {1}".format(args.cluster, args.type))
|
||||
|
||||
# Setup the token for cluster API usage
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Lookup the AppNames and vCenter for unprotected VMs
|
||||
#logging.info("Createing cmdb object for SharePoint list")
|
||||
#cmdb = sharePoint.API()
|
||||
|
||||
# When true, tickets will only be created in ServiceNow dev and JSON output may be expanded to stdout
|
||||
if args.debug:
|
||||
debug = True
|
||||
else:
|
||||
debug = False
|
||||
|
||||
if args.excludeTickets:
|
||||
excludeTickets = True
|
||||
else:
|
||||
excludeTickets = False
|
||||
|
||||
# Create the connection to ServiceNow
|
||||
logging.info("Creating ticketSystem object for ServiceNow")
|
||||
|
||||
if debug:
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
else:
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
|
||||
|
||||
if args.type == "kPhysical":
|
||||
physicalJobs = cluster.GetPhysicalJobs()
|
||||
sys.exit("Warning: Type not implmented for this script.")
|
||||
|
||||
elif args.type == "kSQL":
|
||||
sys.exit("Warning: Type not implmented for this script.")
|
||||
|
||||
elif args.type == "kAzure":
|
||||
# Get all registered subscription sources so we can use the source ids for other REST calls
|
||||
logging.info("Collecting all registered Azure sources")
|
||||
nodes = cluster.GetAZsources()
|
||||
|
||||
# Iterate through all the Azure subscriptions to create one large dataset for all VMs
|
||||
logging.info("Creating dataset for all VMs in our Azure subscriptions")
|
||||
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
|
||||
for node in nodes:
|
||||
hypervisorId = node['protectionSource']['id']
|
||||
|
||||
#Refresh the sources so we ensure we are getting valid information
|
||||
if debug:
|
||||
print("Debug enabled, not refreshing source use -r option to override")
|
||||
else:
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
if args.refresh:
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
myObjects = cluster.GetAZObjects(hypervisorId)
|
||||
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
|
||||
|
||||
# Combine all VM objects into one dictionary
|
||||
for val in myObjects:
|
||||
vmObjects.append(val)
|
||||
|
||||
# Combine all protected objects into one dictionary
|
||||
for val in myProtectedObjects:
|
||||
protectedObjects.append(val)
|
||||
|
||||
# Get all the native snapshot protection jobs that exist in the cluster
|
||||
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
|
||||
sourceJobs = cluster.GetAZNativeJobs()
|
||||
|
||||
elif args.type == "kVMware":
|
||||
# Get all registered vCenter sources so we can use the source ids for other REST calls
|
||||
logging.info("Collecting all registered vmWare sources")
|
||||
nodes = cluster.GetVMsources()
|
||||
|
||||
# Iterate through all the vCenter servers to create one large dataset for all VMs
|
||||
logging.info("Creating dataset for all VMs in our Azure subscriptions")
|
||||
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
|
||||
for node in nodes:
|
||||
hypervisorId = node['protectionSource']['id']
|
||||
|
||||
#Refresh the sources so we ensure we are getting valid information
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
myObjects = cluster.GetVMObjects(hypervisorId)
|
||||
myProtectedObjects = cluster.GetProtectedVMobjects(hypervisorId)
|
||||
|
||||
# Combine all VM objects into one dictionary
|
||||
for val in myObjects:
|
||||
vmObjects.append(val)
|
||||
|
||||
# Combine all protected objects into one dictionary
|
||||
for val in myProtectedObjects:
|
||||
protectedObjects.append(val)
|
||||
|
||||
# At this point we have two sets of data
|
||||
# 1. All vms objects whether they are protected or not ( vmObjects )
|
||||
# 2. All protected vm objects ( protectedObjects )
|
||||
|
||||
# Get all the protection jobs that exist for vmware in the cluster
|
||||
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
|
||||
sourceJobs = cluster.GetVMjobs()
|
||||
|
||||
if args.type == "kVMware" or args.type == "kAzure":
|
||||
|
||||
############################## BEGIN: IDENTIFY EMPTY & POPULATE PAUSED JOBS #######################################
|
||||
# Loop through the sourceJobs to see if they show up in at least once in the list of protected objects.
|
||||
# if they don't show up, we can assume the job is created but empty, which is usually the case for
|
||||
# placeholder VMs
|
||||
|
||||
# In certain circumstances, jobs can be defined but exempt from normal processing. These jobs are normally used from time to time for POC work and do not necessarily need to be protected, here we will load them into the exempt jobs array from the exceptions.yml file.
|
||||
logging.info("Opening exemptions file: vars/exemptions.yml")
|
||||
with open("./vars/exemptions.yml", "r") as exemptionJobData:
|
||||
try:
|
||||
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
|
||||
except:
|
||||
print("Unable to load exemptions file.")
|
||||
|
||||
exemptJobs = []
|
||||
|
||||
logging.info("Parsing exemptions YAML")
|
||||
for i in exemptionJobData:
|
||||
for e in exemptionJobData[i]['jobs']:
|
||||
#exemptJobs.append(e.lower())
|
||||
exemptJobs.append([exemptionJobData[i]['id'],e.lower()])
|
||||
|
||||
# Loop through all the jobs for
|
||||
logging.info("Iterating through all protection jobs")
|
||||
for sourceJob in sourceJobs:
|
||||
|
||||
#Set default control variables for each job
|
||||
isJobExempt = False
|
||||
isJobEmpty = True
|
||||
|
||||
# The REST GetVMjobs function returns unfiltered jobs, so lets get rid of the deleted ones
|
||||
if 'isDeleted' in sourceJob:
|
||||
if sourceJob['isDeleted'] == True:
|
||||
logging.info("--Job {0} is marked for deletion, skipping review".format(sourceJob['name']))
|
||||
continue
|
||||
|
||||
# Look through the exemptJobs listing
|
||||
for entry in exemptJobs:
|
||||
parent = entry[0]
|
||||
pattern = entry[1]
|
||||
|
||||
if parent == sourceJob['parentSourceId']:
|
||||
if re.search(pattern, sourceJob['name'].lower()):
|
||||
isJobExempt = True
|
||||
break
|
||||
|
||||
if isJobExempt == True:
|
||||
if debug == True:
|
||||
logging.info("--Job {0} has been exempt from backups, skipping review".format(sourceJob['name']))
|
||||
print("Job {0} is exempt from tickets".format(sourceJob['name']))
|
||||
continue
|
||||
|
||||
|
||||
# The protection jobs only show tags, they do not show VM ojbects; therefore, to determine if the job is still valid, we have to iterate through all protected objects to see if the job name shows up or not. If it does not show up, then it is assumed to be empty and paused to prevent backup errors. Also, there is usually only one job listed per protected VM; however, there could be cases where a VM is in two or more jobs so we need to check each job listed.
|
||||
# Sample dataset can be seen by using the following url:
|
||||
# https://itdmdndpc01.nd.gov/irisservices/api/v1/public/protectionSources/protectedObjects?environment=kVMware&id=3156
|
||||
logging.info("--Iterating through all protected objects to verify if job {0} is empty".format(sourceJob['name']))
|
||||
for item in protectedObjects:
|
||||
for pJob in item['protectionJobs']:
|
||||
if pJob['name'] == sourceJob['name']:
|
||||
isJobEmpty = False
|
||||
continue
|
||||
|
||||
# Seperate empty from non-empty jobs
|
||||
if isJobEmpty:
|
||||
logging.info("--{0} is empty, pausing future runs".format(sourceJob['name']))
|
||||
emptyJobs.append(sourceJob)
|
||||
# Pause the job so we don't generate erroneous errors.
|
||||
cluster.PauseJob(sourceJob['id'])
|
||||
|
||||
else:
|
||||
# The job is not empty, lets check to see if it is paused so we can create an alert.
|
||||
|
||||
# First we have to check if the isPaused key exists, if it has never been set, it will not.
|
||||
if 'isPaused' in sourceJob:
|
||||
# Now we check to see if isPaused is set to True
|
||||
if sourceJob['isPaused']:
|
||||
logging.info("----{0} is paused but contains VM objects, adding to populatedPausedJobs dataset".format(sourceJob['name']))
|
||||
populatedPausedJobs.append(sourceJob)
|
||||
# isPaused might be defined but it is set to False
|
||||
else:
|
||||
populatedJobs.append(sourceJob)
|
||||
|
||||
# If the job has never been paused, isPaused may not be defined
|
||||
else:
|
||||
populatedJobs.append(sourceJob)
|
||||
|
||||
############################## END: IDENTIFY EMPTY & POPULATE JOBS #######################################
|
||||
logging.info("Iteration of all protection jobs is complete")
|
||||
######################### BEGIN: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
|
||||
|
||||
# This block of code is duplicate logic from above, the populatedPausedJobs array should not contain exempt jobs, we already checked for that. - CC 20230928
|
||||
|
||||
# if debug:
|
||||
# print("Exempt jobs:\n{0}".format(exemptJobs))
|
||||
#
|
||||
# for job in populatedPausedJobs:
|
||||
# isJobExempt = False
|
||||
#
|
||||
# for entry in exemptJobs:
|
||||
# parent = entry[0]
|
||||
# pattern = entry[1]
|
||||
#
|
||||
# if parent == job['parentSourceId']:
|
||||
# if re.search(pattern, job['name'].lower()):
|
||||
# isJobExempt = True
|
||||
# break
|
||||
#
|
||||
# if isJobExempt:
|
||||
# continue
|
||||
# else:
|
||||
|
||||
logging.info("Opening tickets for paused jobs that contain VM objects")
|
||||
for job in populatedPausedJobs:
|
||||
if not excludeTickets:
|
||||
subject = "Backup job " + job['name'] + " is paused"
|
||||
message = "This backup job contains VMs and should not be paused per SLA. Either fix the issue and resume the job, exempt the VM(s) from backups, or decommission the VMs."
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
logging.info("--{0} created for {1}".format(incident['result']['number'],job['name']))
|
||||
######################### END: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
|
||||
|
||||
############################## BEGIN: IDENTIFY UNPROTECTED VMS #################################
|
||||
# RFE: 002
|
||||
# Open the list of documented exceptions, in the long term this may be in the ServiceNow CMDB
|
||||
# if it is, we would have to look it up using the SNOW API
|
||||
with open("./vars/exemptions.yml", "r") as exemptionData:
|
||||
try:
|
||||
exemptionData = yaml.load(exemptionData, Loader=yaml.FullLoader)
|
||||
except:
|
||||
print("Unable to load exemptions file.")
|
||||
|
||||
exemptions = []
|
||||
|
||||
for i in exemptionData:
|
||||
for e in exemptionData[i]['vms']:
|
||||
#exemptions.append([exemptionData[i]['id'],e.lower()])
|
||||
exemptions.append([exemptionData[i]['id'],e])
|
||||
|
||||
# Find protected / unprotected vms
|
||||
logging.info("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
|
||||
for vm in vmObjects:
|
||||
# We don't need to monitor VMs that are marked as a template VM
|
||||
if vm['environment'] == "kVMware":
|
||||
if 'isVmTemplate' in vm['vmWareProtectionSource']:
|
||||
if vm['vmWareProtectionSource']['isVmTemplate'] == True:
|
||||
continue
|
||||
|
||||
# Assume all VM objects are not protected until we prove otherwise
|
||||
isProtected = False
|
||||
|
||||
# Use flag to determine if VM was found or not
|
||||
for pVM in protectedObjects:
|
||||
if vm['name'] == pVM['protectionSource']['name']:
|
||||
isProtected = True
|
||||
protectedVM.append(vm)
|
||||
break
|
||||
|
||||
# The Azure naming convention is different from the vmware naming convention, we need to normalize the names for sharepoint loookups
|
||||
# and exemption lookups
|
||||
|
||||
# SharePoint Removal
|
||||
|
||||
#if vm['environment'] == "kAzure":
|
||||
# if "-" in vm['name']:
|
||||
# nameParts = vm['name'].split("-")
|
||||
# hostname= cmdb.find_VM(nameParts[1])
|
||||
|
||||
# if nameParts[1] == hostname:
|
||||
# hostname = cmdb.find_VM(vm['name'])
|
||||
|
||||
# if hostname == "vm-lcsdev1-1":
|
||||
# hostname = cmdb.get_Node("vm-lcsdev1dev.centralus.cloudapp.azure.com")
|
||||
# else:
|
||||
# hostname = cmdb.find_VM(vm['name'])
|
||||
|
||||
# vm.update({"name": hostname})
|
||||
|
||||
# This part needs some significant improvement on handling the exceptions. Ideally we would want to ensure the
|
||||
# hostname that is excluded belongs to the same parent in the exemption file and in Cohesity.
|
||||
if not isProtected:
|
||||
isExempted = False
|
||||
for entry in exemptions:
|
||||
parent = entry[0]
|
||||
pattern = entry[1]
|
||||
if parent == vm['parentId']:
|
||||
if re.search(pattern, vm['name'].lower()):
|
||||
isExempted = True
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if isExempted == False:
|
||||
unprotectedVM.append(vm)
|
||||
|
||||
############################## END: IDENTIFY UNPROTECTED VMS #################################
|
||||
|
||||
###################### BEGIN: OPEN TICKETS FOR UNPROTECTED & UNEXEMPTED VMS ########################
|
||||
|
||||
# Setup an array to hold unique AppNames
|
||||
unProtectedApps = []
|
||||
for vm in unprotectedVM:
|
||||
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
|
||||
|
||||
###### sharepoint #####
|
||||
#cmdbRecord = cmdb.get_Node(vm['name'])
|
||||
|
||||
###### ServiceNow #####
|
||||
cmdbRecord = ticketSystem.getCMDBItemByFQDN(vm['name'])
|
||||
|
||||
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(vm['parentId']))
|
||||
vcenterName = vcenterSource[0]['protectionSource']['name'].split(".")[0]
|
||||
|
||||
if args.type == "kAzure":
|
||||
if vcenterName == "437b2bfa-850e-4464-b6c2-38a68cda7c69":
|
||||
vcenterName = "prd01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-PRD":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "76297098-764c-43de-8525-c9fda1b237be":
|
||||
vcenterName = "npd01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NPD":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "e53aa0c7-824d-40a2-b420-4ab77b1051d2":
|
||||
vcenterName = "infra01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-INFRA")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-INFRA":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "cb39dbef-0ecf-434d-a8b9-444cef8f390a":
|
||||
vcenterName = "cares01"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-CARES")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-CARES":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
elif vcenterName == "54048952-c2b2-4c65-ab44-157750caa8e6":
|
||||
vcenterName = "NDIT-PaaS"
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NDIT-PaaS")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NDIT-PaaS":
|
||||
jobPolicy=pol['id']
|
||||
|
||||
# Trim off .nd.gov from the name
|
||||
vcenterName = vcenterName.split(".")[0]
|
||||
|
||||
|
||||
##########################################################################################################################
|
||||
# # This was part of the SharePoint & ServiceNow cutover. Since we already pull the snow record above now, we don't
|
||||
# # need to try to look for it again.
|
||||
#
|
||||
if len(cmdbRecord) == 0:
|
||||
# # Handle cases where the record doesn't exist in Sharepoitn yet, but might be going through
|
||||
# # the build process in ServiceNow
|
||||
# try:
|
||||
# print("Trying to find: {}".format(vm['name']))
|
||||
# cmdbData = ticketSystem.getCMDBItemByFQDN(vm['name'])
|
||||
#
|
||||
# print("ServiceNow: {0}, vmware: {1}".format(cmdbData[0]['fqdn'],vm['name']))
|
||||
#
|
||||
# if cmdbData[0]['fqdn'] == vm['name']:
|
||||
# print("Found in Snow CMDB")
|
||||
# inSnow = True
|
||||
# except:
|
||||
# inSnow = False
|
||||
#
|
||||
########################################################################################################################
|
||||
# Open a ticket for unprotected VMs that are not in the CMDB
|
||||
if not excludeTickets:
|
||||
undocumentedVM.append(vm['name'] + " on " + vcenterName)
|
||||
|
||||
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
|
||||
|
||||
print("Undocumented VM: {0} on {1}".format(vm['name'],vcenterName))
|
||||
print("Ticket suppressed")
|
||||
|
||||
'''
|
||||
subject = "Backup job for " + vm['name'] + " on " + vcenterName + " was not found."
|
||||
message = "This VM was not found in the CMDB and is not being backed up.\nInvestigate why the object is not documented in the vmware list, and not being backed up. If necessary have the backup admins exclude the item in the exemptions.yml file"
|
||||
|
||||
12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
jincidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
|
||||
'''
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
# There is a record for the VM in the CMDB so lets construct the appropriate protection group name
|
||||
else:
|
||||
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
|
||||
###### SharePoint #####
|
||||
#vmAppName = cmdb.get_AppName(cmdbRecord[0]['AppNameId'])
|
||||
#vmEnvironment = cmdbRecord[0]['Environment']
|
||||
|
||||
###### ServiceNow #####
|
||||
vmAppName = cmdbRecord[0]['u_nd_application_svc']['name']
|
||||
vmEnvironment = cmdbRecord[0]['environment']
|
||||
|
||||
if args.type == "kVMware":
|
||||
if vmEnvironment == "Production":
|
||||
jobName = vmAppName + "@" + "PRD-" + vcenterName
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-PRD":
|
||||
jobPolicy=pol['id']
|
||||
#jobPolicy="4847477517838800:1610060943809:72255100"
|
||||
|
||||
elif vmEnvironment == "Test":
|
||||
jobName = vmAppName + "@" + "NPD-" + vcenterName
|
||||
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
|
||||
for pol in policyData:
|
||||
if pol['name'] == "ITD-Bronze-NPD":
|
||||
jobPolicy=pol['id']
|
||||
#jobPolicy="4847477517838800:1610060943809:72255060"
|
||||
elif args.type == "kAzure":
|
||||
jobName = "{0}@{1}".format(vmAppName, vcenterName)
|
||||
|
||||
# Don't add the job name to the unprotectedApps array more than once
|
||||
if jobName in unProtectedApps:
|
||||
continue
|
||||
else:
|
||||
# Verify that the job does not already exist in Cohesity
|
||||
verify = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + jobName )
|
||||
|
||||
if len(verify) > 0:
|
||||
for record in verify:
|
||||
if record['name'] != jobName:
|
||||
jobExists = False
|
||||
else:
|
||||
jobExists = True
|
||||
break
|
||||
|
||||
if (len(verify) == 0) or (jobExists == False):
|
||||
unProtectedApps.append(jobName)
|
||||
|
||||
if args.type == "kVMware":
|
||||
tagId = cluster.GetVcenterTagId(vcenterSource, vmAppName)
|
||||
|
||||
if vmEnvironment == "Test":
|
||||
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Production")
|
||||
|
||||
elif vmEnvironment == "Production":
|
||||
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Test")
|
||||
|
||||
if tagId is not None:
|
||||
confirm = "yes"
|
||||
#confirm = input("About to create a protection job for {0}. Are you sure? [yes|no]".format(jobName))
|
||||
if (confirm.lower() == "yes") and (not excludeTickets):
|
||||
print("Creating standard change request for {0}.".format(jobName))
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platform - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
resp = cluster.CreateVMProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId, excludeTag,jobPolicy)
|
||||
postChangeNotes = resp.text
|
||||
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
print("Job {} created, {} complete".format(jobName, changeID['result']['number']['value']))
|
||||
|
||||
if args.type == "kAzure":
|
||||
print("Create backup job for job {0}".format(jobName))
|
||||
print("Looking up Tag ID for {0}".format(vmAppName))
|
||||
tagId = cluster.GetAzureTagId(vcenterSource, vmAppName)
|
||||
|
||||
if (tagId is not None) and (not excludeTickets):
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
resp = cluster.CreateAZProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId)
|
||||
postChangeNotes = resp.text
|
||||
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
|
||||
print("Created {0} for {1}".format(changeID,jobName))
|
||||
|
||||
else:
|
||||
print("Tag not found in Azure: {}".format(vmAppName))
|
||||
subject = "Tag {} was not found in the {} virtualization platform".format(vmAppName, args.type)
|
||||
message = "The application name tag {0} used to create backup job {1} was not found. The ApplicationName tag for {2} must be a case sensitive match with the Sharepoint record.".format(vmAppName, jobName, vm['name'])
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
|
||||
else:
|
||||
if not excludeTickets:
|
||||
subject = "Backup job for " + vm['name'] + " was not found in " + jobName + "."
|
||||
message = "Assign ticket to vmWare or Storage to investigate vm tagging issues."
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
|
||||
incidentID = incident['result']['sys_id']
|
||||
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
|
||||
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
|
||||
|
||||
#itdTeamsMessage.text("Empty Jobs: {0}\nPopulated Jobs: {1}\nPopulated Paused Jobs: {2}\nUnprotected VMs: {3}".format(str(len(emptyJobs)),str(len(populatedJobs)),str(len(populatedPausedJobs)),str(len(unprotectedVM))))
|
||||
#itdTeamsMessage.send()
|
||||
|
||||
body = "Backup Jobs: " + str(len(populatedJobs))
|
||||
body = body + "\nPaused Jobs: " + str(len(populatedPausedJobs))
|
||||
body = body + "\nUnprotected VMs: " + str(len(undocumentedVM))
|
||||
for vm in undocumentedVM:
|
||||
body = body + "\n\t" + vm
|
||||
SendEmail(body, args.cluster)
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-dailyProtectionReview.py', 'Units': 120})
|
||||
|
||||
if debug:
|
||||
array1=[]
|
||||
print("Empty Jobs: " + str(len(emptyJobs)))
|
||||
for value in emptyJobs:
|
||||
print("\t" + value['name'])
|
||||
print("Populated Jobs: " + str(len(populatedJobs)))
|
||||
print("Paused Jobs: " + str(len(populatedPausedJobs)))
|
||||
for value in populatedPausedJobs:
|
||||
print("\t" + value['name'])
|
||||
print("Unprotected VMs: " + str(len(unprotectedVM)))
|
||||
for value in unprotectedVM:
|
||||
array1.append(value['name'] + ", " + str(value['parentId']))
|
||||
print("Sorted Unprotected VMs")
|
||||
array1.sort()
|
||||
for i in array1:
|
||||
print(str(i))
|
||||
|
||||
print("Unprotected Apps")
|
||||
unProtectedApps.sort()
|
||||
for i in unProtectedApps:
|
||||
print(str(i))
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/python
|
||||
#-------------------------------------------------------------------------------------------------------------#
|
||||
# Author: Cliff Cogdill
|
||||
# Description: Review the logs for disk alerts and open a servicenow ticket if one exists.
|
||||
#
|
||||
#
|
||||
#-------------------------------------------------------------------------------------------------------------#
|
||||
import sys,argparse,json,time,smtplib
|
||||
from email.message import EmailMessage
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--debugMode', '-d', action='store_true')
|
||||
parser.add_argument('--days', '-t', type=int, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 diskAlerts.py -c cluster1.domain.tld" )
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -t time duration to look back in days")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
def SendEmail(body, cluster_name):
|
||||
if debugMode:
|
||||
recipients = ['cecogdill@nd.gov']
|
||||
else:
|
||||
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov']
|
||||
|
||||
email = EmailMessage()
|
||||
email['Subject'] = "Cohesity drive failure on {0}".format(cluster_name)
|
||||
email['From'] = "No-Reply@nd.gov"
|
||||
email['To'] = ", ".join(recipients)
|
||||
email.set_content(body)
|
||||
|
||||
with smtplib.SMTP('apprelay.nd.gov') as smtp:
|
||||
smtp.send_message(email)
|
||||
|
||||
print("Sent email")
|
||||
|
||||
# Define variables
|
||||
args = GetArgs()
|
||||
|
||||
if args.debugMode:
|
||||
debugMode = True
|
||||
else:
|
||||
debugMode = False
|
||||
|
||||
# Check for arguments and act accoringly
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Establish a connection to ServiceNow
|
||||
if debugMode:
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
else:
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
# Establish a connection to Cohesity
|
||||
mycluster = cohesity.API(args.cluster)
|
||||
authToken = mycluster.GetAuthToken()
|
||||
mycluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Set lookback period
|
||||
if args.days:
|
||||
d = args.days
|
||||
if d > 0:
|
||||
d = d * -1
|
||||
else:
|
||||
d = -1
|
||||
|
||||
# Get yesterday's start time in unixEpoch:
|
||||
prevWindow = mycluster.GetRelativeTimestamp(d, 0, 0, 0)
|
||||
print(prevWindow)
|
||||
|
||||
# Pull a list of protection runs from yesterday's backup window.
|
||||
diskAlerts = mycluster.GetDiskAlerts(prevWindow)
|
||||
print(diskAlerts)
|
||||
|
||||
# Track disk id so we don't open multiple tickets for the same drive
|
||||
disks = []
|
||||
|
||||
for alert in diskAlerts:
|
||||
|
||||
alert_name = alert['alertDocument']['alertName']
|
||||
|
||||
# Valid alertNames: NewDiskFound, DriveRemoved, DiskOkToRemove, DiskNotHealthy, MarkDiskForRemoval
|
||||
if alert_name == 'DiskNotHealthy' or alert_name == 'MarkDiskForRemoval':
|
||||
for index in alert['propertyList']:
|
||||
if index['key'] == 'disk_id':
|
||||
disk_num = index['value']
|
||||
|
||||
# Ticket has already been opened for this disk
|
||||
if disk_num in disks:
|
||||
continue
|
||||
else:
|
||||
# Create the incident
|
||||
incident = ticketSystem.submitTicket("svccohesityadm", "Cohesity Drive Failure: " + args.cluster , alert['alertDocument']['alertCause'])
|
||||
incidentID = incident['result']['sys_id']
|
||||
|
||||
# Assign the incident to storage
|
||||
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Cloud Platforms')
|
||||
|
||||
message = "Cohesity Drive Failure"
|
||||
body = "Incident: " + incident['result']['number']
|
||||
body = body + "\nFollow Procedure:"
|
||||
body = body + "\n\thttps://northdakota.service-now.com/kb_view.do?sysparm_article=KB0014369"
|
||||
|
||||
# Send the email to the list of recipients in the local SendEmail function
|
||||
SendEmail(body, args.cluster)
|
||||
|
||||
for index in alert['propertyList']:
|
||||
if index['key'] == 'disk_id':
|
||||
disks.append(index['value'])
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-dailyErrors.py', 'Units': 60})
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import automationsAPI as dashboard
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not args.cluster:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Provide a listing of all backedup vmWare objects. VMs should not be listed twice, requires manual review
|
||||
itdvmvc2 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=1")
|
||||
for record in itdvmvc2:
|
||||
if "protectionSource" in record:
|
||||
if "protectionJobs" in record:
|
||||
for jobs in record['protectionJobs']:
|
||||
print(record['protectionSource']['name'] + ", " + jobs['name'])
|
||||
|
||||
itdvmvc1 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=3156")
|
||||
for record in itdvmvc1:
|
||||
if "protectionSource" in record:
|
||||
if "protectionJobs" in record:
|
||||
for jobs in record['protectionJobs']:
|
||||
print(record['protectionSource']['name'] + ", " + jobs['name'])
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-findDuplicates.py', 'Units': 60})
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--type', '-t', type=str, action='store')
|
||||
parser.add_argument('--job', '-j', type=str, action='store')
|
||||
parser.add_argument('--pause', '-p', action='store_true')
|
||||
parser.add_argument('--resume', '-r', action='store_true')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\n python3 pauseBackups.py -c cluster1.domain.tld -v vCenter.domain.tld [ -t kVMware, kPhysical, kSQL ] [ -p | -r ] [ -j protectionJobName ]")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
|
||||
print("\t -t Cohesity job type, kVMware, kPhysical, kSQL")
|
||||
print("\t -j Cohesity job name")
|
||||
print("\t -p Pause jobs that match the specified parameters")
|
||||
print("\t -r Resume jobs that match the specified parameters")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
|
||||
elif args.type == "kVMware" and not args.vcenter:
|
||||
sys.exit("Error: Specify vmware cluster address withe -v parameter when using type of kVMware.")
|
||||
elif args.vcenter and not args.type:
|
||||
args.type = "kVMware"
|
||||
else:
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
if args.type=='kVMware' and args.vcenter:
|
||||
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
|
||||
|
||||
for source in vmSources:
|
||||
if source['protectionSource']['name'] == args.vcenter:
|
||||
vCenter = source
|
||||
print (vCenter)
|
||||
vCenterID = source['protectionSource']['id']
|
||||
break
|
||||
|
||||
if args.job:
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
|
||||
else:
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
print("Attempting to pause all jobs under " + args.vcenter)
|
||||
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
if args.pause:
|
||||
pausedJobs = []
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
print("not vcenter")
|
||||
continue
|
||||
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
|
||||
elif "isPaused" in job:
|
||||
if job['isPaused']:
|
||||
pausedJobs.append(job)
|
||||
continue
|
||||
|
||||
print("Pausing: " + job['name'])
|
||||
resp = cluster.PauseJob(job['id'])
|
||||
|
||||
print("The following jobs were previously paused before this operation.")
|
||||
for pJob in pausedJobs:
|
||||
print(pJob['name'])
|
||||
|
||||
if args.resume:
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
continue
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
print("Resuming: " + job['name'])
|
||||
resp = cluster.ResumeJob(job['id'])
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-pauseOrResumeJob.py', 'Units': 60})
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import sharePointAPI as sharePoint
|
||||
import serviceNowAPI as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--verify', '-v', action='store_true')
|
||||
parser.add_argument('--jobName', '-p', type=str, action='store')
|
||||
parser.add_argument('--newJobName', '-n', type=str, action='store')
|
||||
parser.add_argument('--unDelete', '-r', action='store_true')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v force confirmation on change")
|
||||
print("\t -p Protection job name, use in conjunction with -n")
|
||||
print("\t -n New Protection job name, use in conjunction with -p")
|
||||
print("\t -r Restore job from a deleted status, only used with single job operations")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not args.cluster:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if args.jobName:
|
||||
args.verify = True
|
||||
if not args.newJobName:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# ServiceNow object
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
# Sharepoint Object
|
||||
cmdb = sharePoint.API()
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
if args.jobName:
|
||||
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?names={0}".format(args.jobName))
|
||||
else:
|
||||
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?environments=kVMware&isActive=True")
|
||||
|
||||
for job in vmWareJobs:
|
||||
|
||||
preChangeNotes = json.dumps(job)
|
||||
|
||||
if 'name' in job:
|
||||
jobName = job['name']
|
||||
oldName = job['name']
|
||||
|
||||
|
||||
if args.newJobName:
|
||||
print("\nUpdating job name for {0}".format(jobName))
|
||||
newName = args.newJobName
|
||||
jobData = {"name": newName}
|
||||
job.update(jobData)
|
||||
|
||||
if args.unDelete:
|
||||
print("\nRestoring job {0} from a deleted state.".format(jobName))
|
||||
jobData = {"isDeleted": False}
|
||||
job.update(jobData)
|
||||
|
||||
if '@PRD-itd' not in jobName and '@NPD-itd' not in jobName:
|
||||
print("\nUpdating job name for {0}".format(jobName))
|
||||
x = jobName.split('@')
|
||||
newName = x[0] + "@PRD-" + x[1]
|
||||
jobData = {"name": newName}
|
||||
job.update(jobData)
|
||||
|
||||
print("Adding the exclude Test tag")
|
||||
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(job['parentSourceId']))
|
||||
testTagId = cluster.GetVcenterTagId(vcenterSource, 'Test')
|
||||
excludeData = {"excludeVmTagIds": [[testTagId]]}
|
||||
job.update(excludeData)
|
||||
|
||||
print("Updating to the new Policy Id")
|
||||
policy = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
|
||||
policyData = {"policyId": policy[0]['id']}
|
||||
job.update(policyData)
|
||||
|
||||
if args.verify:
|
||||
confirm = input("About to update protection job {0}. Are you sure? [yes|no] ".format(oldName))
|
||||
else:
|
||||
confirm = "yes"
|
||||
|
||||
if confirm.lower() == "yes":
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Computer Systems Storage", "svccohesityadm", "ndheck", "mlaverdure", "Computer Systems", "Backup/Restore", "Automated Change - Cohesity job update for {}".format(oldName), "Split production and non-production servers into multiple groups.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, "Original job details\n\n{}".format(preChangeNotes))
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
updateStats = cluster.UpdateVMProtectionJob(job)
|
||||
|
||||
if updateStats.status_code != 200 and updateStats.status_code != 201:
|
||||
postChangeNotes = "Job update failed\n\n" + updateStats.text
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Unsuccessful", "Change Failed")
|
||||
else:
|
||||
postChangeNotes = "Updated job name, added the exclude test tag, and updated the policy\n\n" + updateStats.text
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
|
||||
print("Chagne {} complete".format(changeID['result']['number']['value']))
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as serviceNow
|
||||
import sharePointAPI as sharePoint
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--server', '-s', type=str, action='store')
|
||||
parser.add_argument('--protectGroup', '-p', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 serverRegistration.py -s hostname.nd.gov -p MyApp@physical")
|
||||
print("\t -s: Fully qualified domain name of the Server")
|
||||
print("\t -p: Protection Group the server will be in")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if not (args.server and args.protectGroup):
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Proceed with registration
|
||||
if (args.server and args.protectGroup):
|
||||
try:
|
||||
mdn = cohesity.API('itdmdndpc01.nd.gov')
|
||||
mdnToken = mdn.GetAuthToken()
|
||||
mdn.UpdateHeaders(mdnToken['accessToken'])
|
||||
|
||||
newHost = args.server
|
||||
protectionGroup = args.protectGroup
|
||||
|
||||
isPhysicalRegistered = bool()
|
||||
|
||||
# Get the physical server objects in Cohesity
|
||||
physicalServers = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
|
||||
|
||||
for nodes in physicalServers['rootNodes']:
|
||||
if nodes['rootNode']['name'] == newHost:
|
||||
print(newHost + " is already registerd as a physical server.")
|
||||
isPhysicalRegistered = bool("true")
|
||||
physicalSourceId = nodes['rootNode']['id']
|
||||
break
|
||||
|
||||
if not isPhysicalRegistered:
|
||||
attempt = mdn.RegisterPhysical(newHost)
|
||||
print("Registering " + newHost + " as a Physical Server ")
|
||||
time.sleep(20)
|
||||
try:
|
||||
physicalSourceId = attempt['id']
|
||||
except:
|
||||
print("\nCould not reach " + newHost + " on port 50051. Please verify the agent is running and that all firewall rules are accounted for.")
|
||||
exit(1)
|
||||
|
||||
# Get the list of protection groups & add the new one if necessary
|
||||
protectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments[0]=kPhysical")
|
||||
uniqueJobs = {job['id'] : job for job in protectionJobs}.values()
|
||||
jobExists = bool()
|
||||
isProtected = bool()
|
||||
jobSources = []
|
||||
|
||||
for job in uniqueJobs:
|
||||
# Source ID cannot be in more then on protection group for SQL
|
||||
if ('sourceIds' in job) and (physicalSourceId in job['sourceIds'] and ("DELETED" not in job['name']) ):
|
||||
print(newHost + " is already in " + job['name'] + " and cannot be in multiple groups.")
|
||||
isProtected = bool("true")
|
||||
jobExists = bool("true")
|
||||
break
|
||||
|
||||
# The source ID was not found in any other SQL job, but a job name already exsits, so lets collect all sources in the job.
|
||||
elif (job['name'] == protectionGroup) and ( physicalSourceId not in job['sourceIds']):
|
||||
jobExists = bool("true")
|
||||
break
|
||||
|
||||
if jobExists == bool('true') and isProtected != bool('true'):
|
||||
print("Adding host " + newHost + " to protection group " + protectionGroup)
|
||||
|
||||
# By default we will protect all local drives
|
||||
sourceParams = {
|
||||
"sourceId": physicalSourceId,
|
||||
"physicalSpecialParameters": {
|
||||
"filePaths": [
|
||||
{
|
||||
"backupFilePath":"$ALL_LOCAL_DRIVES",
|
||||
"skipNestedVolumes":True
|
||||
}
|
||||
],
|
||||
"usesSkipNestedVolumesVec":True
|
||||
}
|
||||
}
|
||||
|
||||
job["sourceIds"].append(physicalSourceId)
|
||||
job["sourceSpecialParameters"].append(sourceParams)
|
||||
resp = mdn.UpdateProtectionJob(job)
|
||||
print(resp.content)
|
||||
|
||||
if jobExists == bool():
|
||||
print("Creating protection job for " + protectionGroup + " and adding " + newHost)
|
||||
################ TO DO ###############
|
||||
# Defaults from JSON dump of jobs objects
|
||||
resp = mdn.CreatePhysicalProtectionJob(physicalSourceId, protectionGroup)
|
||||
print(resp.content)
|
||||
|
||||
except OSError as cohesityError:
|
||||
print('Cohesity Error: ' + cohesityError)
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-registerServer.py', 'Units': 30})
|
||||
@@ -0,0 +1,86 @@
|
||||
ansible==5.9.0
|
||||
ansible-core==2.12.10
|
||||
argcomplete==2.0.0
|
||||
bcrypt==3.2.2
|
||||
Beaker==1.10.0
|
||||
beautifulsoup4==4.11.0
|
||||
blivet==3.4.4
|
||||
blivet-gui==2.3.0
|
||||
Brlapi==0.8.4
|
||||
certifi==2023.11.17
|
||||
cffi==1.15.0
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.0.11
|
||||
click==8.0.4
|
||||
cryptography==36.0.0
|
||||
cupshelpers==1.0
|
||||
dasbus==1.6
|
||||
dbus-python==1.2.18
|
||||
distro==1.6.0
|
||||
fedora-third-party==0.10
|
||||
fros==1.1
|
||||
gpg==1.17.0
|
||||
humanize==3.13.1
|
||||
idna==3.3
|
||||
Jinja2==3.0.3
|
||||
langtable==0.0.61
|
||||
libcomps==0.1.18
|
||||
lxml==4.7.1
|
||||
Mako==1.1.4
|
||||
MarkupSafe==2.1.1
|
||||
nftables==0.1
|
||||
ntlm-auth==1.5.0
|
||||
olefile==0.46
|
||||
packaging==21.3
|
||||
paramiko==2.12.0
|
||||
Paste==3.5.0
|
||||
pathspec==0.10.1
|
||||
pexpect==4.8.0
|
||||
pid==2.2.3
|
||||
Pillow==9.1.0
|
||||
ply==3.11
|
||||
productmd==1.33
|
||||
ptyprocess==0.6.0
|
||||
pwquality==1.4.4
|
||||
pyasn1==0.4.8
|
||||
pycairo==1.21.0
|
||||
pycparser==2.20
|
||||
pycrypto==2.6.1
|
||||
pycups==2.0.1
|
||||
pycurl==7.45.1
|
||||
pyenchant==3.2.2
|
||||
PyGObject==3.42.1
|
||||
pykickstart==3.36
|
||||
PyNaCl==1.4.0
|
||||
pyOpenSSL==21.0.0
|
||||
pyparsing==2.4.7
|
||||
pyparted==3.12.0
|
||||
PySocks==1.7.1
|
||||
python-augeas==1.1.0
|
||||
python-dateutil==2.8.1
|
||||
python-meh==0.50
|
||||
pytz==2022.7
|
||||
pyudev==0.22.0
|
||||
pywinrm==0.4.1
|
||||
pyxdg==0.27
|
||||
PyYAML==6.0
|
||||
regex==2022.10.31
|
||||
requests==2.27.1
|
||||
requests-file==1.5.1
|
||||
requests-ftp==0.3.1
|
||||
requests-ntlm==1.1.0
|
||||
resolvelib==0.5.5
|
||||
rpm==4.17.1
|
||||
selinux==3.3
|
||||
sepolicy==3.3
|
||||
setools==4.4.0
|
||||
simpleaudio==1.0.4
|
||||
simpleline==1.9.0
|
||||
six==1.16.0
|
||||
sos==4.3
|
||||
soupsieve==2.3.1
|
||||
systemd-python==234
|
||||
Tempita==0.5.2
|
||||
urllib3==1.26.12
|
||||
xmltodict==0.12.0
|
||||
yamllint==1.28.0
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
Allows for the registration of a SQL Server VM to Cohesity
|
||||
"""
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import itdSnow as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--server', '-s', type=str, action='store')
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--protectGroup', '-p', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
parser.add_argument('--lookupGroup', '-l', action='store_true')
|
||||
parser.add_argument('--audit', '-a', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 sqlServerRegistration.py -s hostname.nd.gov -p MyApp@SQL")
|
||||
print("\t -a: Audit SQL objects and re-apply permissions")
|
||||
print("\t -c: The FQDN of the cluster to register the client to")
|
||||
print("\t -d: Run the registration in debug mode")
|
||||
print("\t -l: Lookup appName in Sharepoint and propose Protection Group name")
|
||||
print("\t -s: Fully qualified domain name of the Virtualized SQL Server")
|
||||
print("\t -p: Protection Group the server will be in")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
protectionGroup = ''
|
||||
sqlHost=''
|
||||
|
||||
# Establish a connection to ServiceNow
|
||||
if args.debug:
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
else:
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not ((args.server and (args.lookupGroup or args.protectGroup)) or (args.audit)):
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Get the AppName from the VM List in SharePoint
|
||||
if args.server and args.lookupGroup:
|
||||
sqlHost = args.server
|
||||
cmdb_record = ticketSystem.getCMDBItemByFQDN(sqlHost)
|
||||
print(json.dumps(cmdb_record,indent=4))
|
||||
appName = cmdb_record[0]['u_nd_application_svc']['name']
|
||||
|
||||
# shpt = sharePoint.API()
|
||||
# vmRecord = shpt.get_Node(sqlHost)
|
||||
# appName = shpt.get_AppName(vmRecord[0]['AppNameId'])
|
||||
appName = appName + "@SQL"
|
||||
|
||||
print("\nSuggested protection group name is: " + appName)
|
||||
|
||||
menuControl = 0
|
||||
|
||||
while menuControl == 0:
|
||||
contRegistration = input("Would you like to continue? [y/n]")
|
||||
|
||||
if contRegistration == 'y' or contRegistration == 'Y':
|
||||
menuControl = 1
|
||||
print("\nProceeding with registration.")
|
||||
protectionGroup = appName
|
||||
|
||||
elif contRegistration == 'n' or contRegistration == 'N':
|
||||
menuControl = 1
|
||||
print("\nAuto lookup registration has been cancelled.")
|
||||
print("\nPlease relaunch application with -s & -p parameters to specify a custom protection group.")
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
contRegistration = input("Would you like to continue? [y/n]")
|
||||
|
||||
# Proceed with registration using either the SharePoint lookup value or the user specified value
|
||||
if (((args.server or sqlHost) and (args.protectGroup or protectionGroup)) or args.audit):
|
||||
try:
|
||||
mdn = cohesity.API(args.cluster)
|
||||
mdnToken = mdn.GetAuthToken()
|
||||
mdn.UpdateHeaders(mdnToken['accessToken'])
|
||||
|
||||
if (not sqlHost) and (not protectionGroup):
|
||||
sqlHost = args.server
|
||||
protectionGroup = args.protectGroup
|
||||
|
||||
isSQLRegistered = bool()
|
||||
isPhysicalRegistered = bool()
|
||||
|
||||
# Get the VMs and SQL objects in Cohesity
|
||||
#vms = mdn.GetRequest("/public/protectionSources/virtualMachines")
|
||||
registeredSQL = mdn.GetFilteredRequest("/public/protectionSources/applicationServers", "?application=kSQL")
|
||||
physicalServers = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
|
||||
|
||||
for nodes in physicalServers['rootNodes']:
|
||||
if nodes['rootNode']['name'] == sqlHost:
|
||||
print(sqlHost + " is already registerd as a physical server.")
|
||||
isPhysicalRegistered = bool("true")
|
||||
physicalSourceId = nodes['rootNode']['id']
|
||||
break
|
||||
|
||||
# Check to see if the source is already registered as a SQL Server
|
||||
for sqlServer in registeredSQL:
|
||||
if sqlServer['applicationServer']['protectionSource']['name'] == sqlHost:
|
||||
isSQLRegistered = bool("true")
|
||||
print(sqlHost + " is already registerd as a SQL server.")
|
||||
break
|
||||
|
||||
if (sqlHost):
|
||||
if not isPhysicalRegistered:
|
||||
attempt = mdn.RegisterPhysical(sqlHost)
|
||||
print("Registering " + sqlHost + " as a Physical Server ")
|
||||
time.sleep(20)
|
||||
try:
|
||||
physicalSourceId = attempt['id']
|
||||
except:
|
||||
print("\nCould not reach " + sqlHost + " on port 50051. Please verify the agent is running and that all firewall rules are accounted for.")
|
||||
exit(1)
|
||||
|
||||
if not isSQLRegistered:
|
||||
print("Registering " + sqlHost + " as a SQL Server ")
|
||||
attempt = mdn.RegisterSQL(physicalSourceId)
|
||||
time.sleep(20)
|
||||
|
||||
# Verify DBAs have permissions to all SQL objects, including newly registered objects
|
||||
registeredSQL = mdn.GetFilteredRequest("/public/protectionSources/applicationServers", "?application=kSQL")
|
||||
|
||||
# Filter out unique objects
|
||||
uniqueSQLSources = {sql['applicationServer']['protectionSource']['id'] : sql for sql in registeredSQL}.values()
|
||||
|
||||
# Array to hold protection source IDs
|
||||
sqlProtectionSourceIds = []
|
||||
|
||||
for sqlServer in uniqueSQLSources:
|
||||
sqlProtectionSourceIds.append(sqlServer['applicationServer']['protectionSource']['id'])
|
||||
|
||||
# Send protection source IDs for DBA group
|
||||
dbaGroup = mdn.GetFilteredRequest("/public/groups", "?name=ITD-COHESITY-DBA")
|
||||
dbaSid = dbaGroup[0]['sid']
|
||||
print("Sending Request to Cohesity to update DBA permissions")
|
||||
attempt = mdn.UpdatePermissions(sqlProtectionSourceIds, dbaSid)
|
||||
|
||||
# Get the list of protection groups & add the new one if necessary
|
||||
if (sqlHost):
|
||||
sqlProtectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments=kSQL")
|
||||
uniqueJobs = {job['id'] : job for job in sqlProtectionJobs}.values()
|
||||
jobExists = bool()
|
||||
isProtected = bool()
|
||||
isPaused=bool()
|
||||
jobSources = []
|
||||
|
||||
for job in uniqueJobs:
|
||||
# Source ID cannot be in more then on protection group for SQL
|
||||
if ('sourceIds' in job) and (physicalSourceId in job['sourceIds'] and ("DELETED" not in job['name']) ):
|
||||
print(sqlHost + " is already in " + job['name'] + " and cannot be in multiple groups.")
|
||||
isProtected = bool("true")
|
||||
jobExists = bool("true")
|
||||
break
|
||||
|
||||
# The source ID was not found in any other SQL job, but a job name already exsits, so lets collect all sources in the job.
|
||||
elif (job['name'] == protectionGroup) and ( physicalSourceId not in job['sourceIds']):
|
||||
jobExists = bool("true")
|
||||
break
|
||||
|
||||
if jobExists == bool('true') and isProtected != bool('true'):
|
||||
isPaused=bool('true')
|
||||
job["sourceIds"].append(physicalSourceId)
|
||||
resp = mdn.UpdateProtectionJob(job)
|
||||
print(resp.content)
|
||||
|
||||
if jobExists == False:
|
||||
print("Creating protection job for " + protectionGroup + " and adding " + sqlHost)
|
||||
################ TO DO ###############
|
||||
# Defaults from JSON dump of jobs objects
|
||||
if args.cluster == 'itdazdpc02.nd.gov':
|
||||
resp = mdn.CreateAZSQLProtectionJob(physicalSourceId, protectionGroup)
|
||||
else:
|
||||
resp = mdn.CreateSQLProtectionJob(physicalSourceId, protectionGroup)
|
||||
print(resp.content)
|
||||
|
||||
except OSError as cohesityError:
|
||||
print('Cohesity Error: ' + cohesityError)
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-sqlServerRegistration.py', 'Units': 30})
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import sharePointAPI as sharePoint
|
||||
import serviceNowAPI as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not args.cluster:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# ServiceNow object
|
||||
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
|
||||
|
||||
# Sharepoint Object
|
||||
cmdb = sharePoint.API()
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Pull information from Cohestiy API
|
||||
itdvmvc1 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=3156")
|
||||
for record in itdvmvc1:
|
||||
if "protectionSource" in record:
|
||||
if "protectionJobs" in record:
|
||||
for jobs in record['protectionJobs']:
|
||||
print(record['protectionSource']['name'] + ", " + jobs['name'])
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.insert(0, './classes/')
|
||||
import cohesityAPI as cohesity
|
||||
import serviceNowAPI as serviceNow
|
||||
import sharePointAPI as sharePoint
|
||||
import automationsAPI as dashboard
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--server', '-s', type=str, action='store')
|
||||
parser.add_argument('--protectGroup', '-p', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 serverRegistration.py -s hostname.nd.gov -p MyApp@physical")
|
||||
print("\t -s: Fully qualified domain name of the Server")
|
||||
print("\t -p: Protection Group the server is in")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit()
|
||||
|
||||
if args.debug:
|
||||
debugMode = True
|
||||
else:
|
||||
debugMode = False
|
||||
|
||||
cluster = cohesity.API('itdmdndpc01.nd.gov')
|
||||
clusterToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(clusterToken['accessToken'])
|
||||
|
||||
sourceId = cluster.GetProtectionSourceId(args.server)
|
||||
pJob = cluster.GetProtectionJobByHost(args.server)
|
||||
|
||||
if debugMode:
|
||||
print("SourceId:\n==========")
|
||||
print(sourceId)
|
||||
|
||||
print("Job Info:\n==========")
|
||||
print(pJob)
|
||||
|
||||
# If the source is not tied to a protection job we will remove it anyway.
|
||||
if pJob == -1:
|
||||
print("No job was found, continuing to unregister server.")
|
||||
removeSourceStat = cluster.RemoveProtectionSource(sourceId)
|
||||
|
||||
else:
|
||||
if sourceId in pJob['sourceIds']:
|
||||
ids = pJob['sourceIds']
|
||||
ids.remove(sourceId)
|
||||
pJob.update({'sourceIds': ids})
|
||||
|
||||
# Check to see if there are any specific source parameters, if so ensure we clean up those entries as well.
|
||||
if 'sourceSpecialParameters' in pJob:
|
||||
specialParams = pJob['sourceSpecialParameters']
|
||||
|
||||
for entry in specialParams:
|
||||
if entry['sourceId'] == sourceId:
|
||||
specialParams.remove(entry)
|
||||
pJob.update({'sourceSpecialParameters': specialParams})
|
||||
|
||||
# Send the updated job information back to the cluster for updating
|
||||
updateJobStat = cluster.UpdateProtectionJob(pJob)
|
||||
|
||||
# Ensure the job was updated successfully
|
||||
if updateJobStat.status_code != 200:
|
||||
if debugMode:
|
||||
print(updateJobStat)
|
||||
print(updateJobStat.content)
|
||||
sys.exit("Updating the protection job " + pJob['name'] + " failed.\n\nThe source " + args.server + " may be the only source for this job or have special parameters set in the protection job, manual intervention is required to clean up the protection group according to NDIT SLAs.")
|
||||
|
||||
# Attempt to remove the source from the registered sources
|
||||
removeSourceStat = cluster.RemoveProtectionSource(sourceId)
|
||||
|
||||
# Ensure the source was removed successfully
|
||||
if removeSourceStat.status_code == 403:
|
||||
if debugMode:
|
||||
print(removeSourceStat)
|
||||
print(removeSourceStat.content)
|
||||
sys.exit("Removing " + args.server + " from " + cluster.GetClusterName() + " has failed.")
|
||||
print(args.server + " has been unregistered from " + cluster.GetClusterName())
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'DeProvisioning', 'Platform': 'Python-unRegisterServer.py', 'Units': 30})
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import sharePointAPI as sharePoint
|
||||
import serviceNowAPI as snow
|
||||
import automationsAPI as dashboard
|
||||
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--verify', '-v', action='store_true')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v force confirmation on change")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
if not args.cluster:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# ServiceNow object
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
|
||||
# Sharepoint Object
|
||||
cmdb = sharePoint.API()
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?environments=kAzureNative&isActive=True")
|
||||
|
||||
for job in vmWareJobs:
|
||||
preChangeNotes = json.dumps(job)
|
||||
if 'name' in job:
|
||||
jobName = job['name']
|
||||
|
||||
if '@cares01' in jobName:
|
||||
continue
|
||||
print("\nUpdating policy for {0}".format(jobName))
|
||||
policyData = {"policyId": "7780085755317378:1694019083558:285"
|
||||
|
||||
if '@infra01' in jobName:
|
||||
continue
|
||||
print("\nUpdating policy for {0}".format(jobName))
|
||||
policyData = {"policyId": "7780085755317378:1694019083558:286"}
|
||||
job.update(policyData)
|
||||
|
||||
elif '@npd01' in jobName:
|
||||
continue
|
||||
print("\nUpdating policy for {0}".format(jobName))
|
||||
policyData = {"policyId": "7780085755317378:1694019083558:287"}
|
||||
job.update(policyData)
|
||||
|
||||
elif '@prd01' in jobName:
|
||||
print("\nUpdating policy for {0}".format(jobName))
|
||||
policyData = {"policyId": "7780085755317378:1694019083558:288"}
|
||||
job.update(policyData)
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
if args.verify:
|
||||
confirm = input("About to update protection job {0}. Are you sure? [yes|no] ".format(jobName))
|
||||
else:
|
||||
confirm = "yes"
|
||||
|
||||
if confirm.lower() == "yes":
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Computer Systems", "Backup/Restore", "Automated Change - Cohesity job update for {}".format(jobName), "Update protection policy to enable replication.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, "Original job details\n\n{}".format(preChangeNotes))
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
updateStats = cluster.UpdateVMProtectionJob(job)
|
||||
|
||||
if updateStats.status_code != 200 and updateStats.status_code != 201:
|
||||
postChangeNotes = "Job update failed\n\n" + updateStats.text
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Unsuccessful", "Change Failed")
|
||||
else:
|
||||
postChangeNotes = "Updated the policy\n\n" + updateStats.text
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
|
||||
print("Change {} complete".format(changeID['result']['number']['value']))
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import automationsAPI as dashboard
|
||||
import nditServiceNow as snow
|
||||
import random
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
|
||||
global tagName
|
||||
tagName = None
|
||||
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--tag', '-t', type=str, action='store')
|
||||
parser.add_argument('--group', '-g', type=str, action='store')
|
||||
parser.add_argument('--action', '-a', type=str, action='store')
|
||||
parser.add_argument('--list', '-l', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 updateProtectionGroupTags.py -c hostname.nd.gov")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -g Protection group name")
|
||||
print("\t -a Action to perform 'add' or 'remove'")
|
||||
print("\t -l List that needs to be updated, the 'include' or 'exclude'")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
|
||||
|
||||
|
||||
vmObjects = []
|
||||
protectedObjects = []
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
|
||||
nodes = cluster.GetAZsources()
|
||||
|
||||
subscription = input("Which subscription do you want to update? [prd01,npd01,cares01,infra01] ")
|
||||
if subscription == 'npd01':
|
||||
subId = 1
|
||||
plcyId = "7780085755317378:1694019083558:287"
|
||||
|
||||
elif subscription == 'infra01':
|
||||
subId = 28
|
||||
plcyId = "7780085755317378:1694019083558:286"
|
||||
|
||||
elif subscription == 'prd01':
|
||||
subId = 37
|
||||
plcyId = "7780085755317378:1694019083558:288"
|
||||
|
||||
elif subscription == 'cares01':
|
||||
subId = 4
|
||||
plcyId = "7780085755317378:1694019083558:285"
|
||||
|
||||
sId = subId
|
||||
|
||||
nodes = cluster.GetAZsources()
|
||||
|
||||
for node in nodes:
|
||||
|
||||
if node['protectionSource']['id'] == subscription:
|
||||
hypervisorId = node['protectionSource']['id']
|
||||
cluster.RefreshSource(node['protectionSource']['id'])
|
||||
|
||||
myObjects = cluster.GetAZObjects(hypervisorId)
|
||||
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
|
||||
|
||||
for val in myObjects:
|
||||
vmObjects.append(val)
|
||||
|
||||
for val in myProtectedObjects:
|
||||
protectedObjects.append(val)
|
||||
|
||||
sourceJobs = cluster.GetAZNativeJobs()
|
||||
|
||||
for job in sourceJobs:
|
||||
|
||||
if job['parentSourceId'] != subId:
|
||||
continue
|
||||
|
||||
print("\nPreparing to update {0}".format(job['name']))
|
||||
tagName = job['name'].split('@')[0]
|
||||
subName = job['name'].split('@')[1]
|
||||
|
||||
#confirm = input("The tag to protect for this job is {0}. Is that correct? [y/n]".format(tagName))
|
||||
confirm = 'y'
|
||||
|
||||
while confirm != 'y' and confirm != 'n':
|
||||
confirm = input("Please enter 'y' or 'n': ")
|
||||
|
||||
if confirm == 'n':
|
||||
tagName = input("Enter the correct tag: ")
|
||||
elif confirm == 'y':
|
||||
|
||||
subSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(sId))
|
||||
tagId = cluster.GetAzureTagId(subSource, tagName)
|
||||
|
||||
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Update protection job {0}".format(job['name']), "Adjust start time.")
|
||||
changeSysID = changeID['result']['sys_id']['value']
|
||||
ticketSystem.scheduleStandardChange(changeSysID)
|
||||
ticketSystem.implementStandardChange(changeSysID)
|
||||
ticketSystem.reviewStandardChange(changeSysID)
|
||||
|
||||
rmin = [0,15,30,45]
|
||||
smin = random.choice(rmin)
|
||||
|
||||
rhr = [17,18,19,20,21,22,23,0,1,2,3,4,5]
|
||||
shr = random.choice(rhr)
|
||||
|
||||
if(tagId is not None):
|
||||
data = {
|
||||
"name": job['name'],
|
||||
"description": "",
|
||||
"policyId": plcyId,
|
||||
"storageDomainId": 36,
|
||||
"startTime": {
|
||||
"hour": shr,
|
||||
"minute": smin,
|
||||
"timezone": "America/Chicago"
|
||||
},
|
||||
"priority": "kMedium",
|
||||
"sla": [
|
||||
{
|
||||
"backupRunType": "kIncremental",
|
||||
"slaMinutes": 480
|
||||
},
|
||||
{
|
||||
"backupRunType": "kFull",
|
||||
"slaMinutes": 480
|
||||
}
|
||||
],
|
||||
"isPaused": False,
|
||||
"abortInBlackoutPeriod": False,
|
||||
"environment": "kAzure",
|
||||
"azureParams": {
|
||||
"protectionType": "kNative",
|
||||
"nativeProtectionTypeParams":{
|
||||
"objects": [],
|
||||
"vmTagIds": [
|
||||
[
|
||||
tagId
|
||||
]
|
||||
],
|
||||
#"excludeVmTagIds": False,
|
||||
"indexingPolicy":{
|
||||
"enableIndexing": True,
|
||||
"includePaths": [
|
||||
"/"
|
||||
],
|
||||
"excludePaths": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/splunk",
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
"abortInBlackoutPeriod": False
|
||||
}
|
||||
|
||||
job.update(data)
|
||||
resp = cluster.UpdateAZprotectionJob(job)
|
||||
postChangeNotes = resp.text
|
||||
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
|
||||
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
|
||||
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 10})
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--job', '-j', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
|
||||
print("\nExample:")
|
||||
print(r"$ export COHESITY_USER=\"NDGOV\jDoe\"")
|
||||
print("$ export COHESITY_PASS=\"1Lik3Jane\"")
|
||||
print("\n python3 <SCRIPT_NAME> -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
|
||||
print("\t -j Cohesity job name")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
|
||||
else:
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
if args.vcenter:
|
||||
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
|
||||
|
||||
for source in vmSources:
|
||||
if source['protectionSource']['name'] == args.vcenter:
|
||||
vCenter = source
|
||||
vCenterID = source['protectionSource']['id']
|
||||
break
|
||||
|
||||
if args.job:
|
||||
# Get the details of a single job
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
|
||||
else:
|
||||
# Get the details of all jobs under a vCenter
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
# Do stuff with the JSON data
|
||||
# Example:
|
||||
if args.pause:
|
||||
pausedJobs = []
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
print("not vcenter")
|
||||
continue
|
||||
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
|
||||
elif "isPaused" in job:
|
||||
if job['isPaused']:
|
||||
pausedJobs.append(job)
|
||||
continue
|
||||
|
||||
print("Pausing: " + job['name'])
|
||||
resp = cluster.PauseJob(job['id'])
|
||||
|
||||
print("The following jobs were previously paused before this operation.")
|
||||
for pJob in pausedJobs:
|
||||
print(pJob['name'])
|
||||
|
||||
if args.resume:
|
||||
for job in uniqueJobs:
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
continue
|
||||
|
||||
elif "isDeleted" in job:
|
||||
continue
|
||||
|
||||
print("Resuming: " + job['name'])
|
||||
resp = cluster.ResumeJob(job['id'])
|
||||
|
||||
elif args.job:
|
||||
job = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
|
||||
|
||||
job[0].update({"indexingPolicy":{
|
||||
"disableIndexing": False,
|
||||
"allowPrefixes": [
|
||||
"/"
|
||||
],
|
||||
"denyPrefixes": [
|
||||
"/$Recycle.Bin",
|
||||
"/Windows",
|
||||
"/ProgramData",
|
||||
"/System Volume Information",
|
||||
"/Users/*/AppData",
|
||||
"/Recovery",
|
||||
"/usr",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/lib",
|
||||
"/grub",
|
||||
"/grub2",
|
||||
"/opt/splunk",
|
||||
"/splunk",
|
||||
]
|
||||
}})
|
||||
|
||||
resp = cluster.UpdateVMProtectionJob(job[0])
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
import automationsAPI as dashboard
|
||||
|
||||
# Global variables that will be used across all functions
|
||||
global tagid
|
||||
tagid = None
|
||||
|
||||
global tagName
|
||||
tagName = None
|
||||
|
||||
# Begin Functions
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--cluster', '-c', type=str, action='store')
|
||||
parser.add_argument('--vcenter', '-v', type=str, action='store')
|
||||
parser.add_argument('--tag', '-t', type=str, action='store')
|
||||
parser.add_argument('--group', '-g', type=str, action='store')
|
||||
parser.add_argument('--action', '-a', type=str, action='store')
|
||||
parser.add_argument('--list', '-l', type=str, action='store')
|
||||
parser.add_argument('--help', '-h', action='store_true')
|
||||
return (parser.parse_args())
|
||||
|
||||
def PrintHelp():
|
||||
print("\nBasic Usage:")
|
||||
print("\npython3 updateProtectionGroupTags.py -c hostname.nd.gov -p MyApp@SQL")
|
||||
print("\t -c FQDN of Cohesity cluster address")
|
||||
print("\t -v FQDN of vCenter Server")
|
||||
print("\t -t vmWare Tag Name")
|
||||
print("\t -g Protection group name")
|
||||
print("\t -a Action to perform 'add' or 'remove'")
|
||||
print("\t -l List that needs to be updated, the 'include' or 'exclude'")
|
||||
print("\t -h Prints this help message")
|
||||
|
||||
def GetTagID(sources):
|
||||
globalList = globals()
|
||||
|
||||
for source in sources:
|
||||
if globalList['tagid'] is None:
|
||||
GetNode(sources)
|
||||
|
||||
def GetNode(record):
|
||||
globalList = globals()
|
||||
|
||||
# A non 'node' element was found, check each child key to see if one contains 'kTag'
|
||||
if "protectionSource" in record:
|
||||
if "vmWareProtectionSource" in record['protectionSource']:
|
||||
if record['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
|
||||
|
||||
# Tag elements have been found, look for the correct tag and assign the id to the global variable
|
||||
if record['protectionSource']['vmWareProtectionSource']['name'] == globalList['tagName']:
|
||||
|
||||
# Tag was found based on the name
|
||||
globalList['tagid'] = record['protectionSource']['id']
|
||||
print('Tag was found with ID: ' + str(record['protectionSource']['id']))
|
||||
|
||||
# Recurse through all 'nodes' elements looking for kTag
|
||||
if "nodes" in record:
|
||||
for node in record['nodes']:
|
||||
|
||||
# The tag hasn't been found, search the next record
|
||||
if globalList['tagid'] is None:
|
||||
GetNode(node)
|
||||
|
||||
# The tag was found and the global id value was set, stop iterating.
|
||||
else:
|
||||
break
|
||||
|
||||
def UpdateProtectionGroupExcludes(group, action):
|
||||
globalList = globals()
|
||||
isExcluded = False
|
||||
tagArray = []
|
||||
|
||||
if "excludeVmTagIds" in group:
|
||||
for index in group['excludeVmTagIds']:
|
||||
for tag in index:
|
||||
tagArray.append(tag)
|
||||
if tag == globalList['tagid']:
|
||||
isExcluded = True
|
||||
|
||||
# Add the tag to the exclusion
|
||||
if action == 'add':
|
||||
if isExcluded == True:
|
||||
print(globalList['tagName'] + " tag is already excluded for " + group['name'])
|
||||
else:
|
||||
print("Adding exclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
|
||||
tagArray.append(globalList['tagid'])
|
||||
|
||||
# Remove the tag from the exclusion
|
||||
elif action == 'remove':
|
||||
if isExcluded == True:
|
||||
print("Removing exclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
|
||||
tagArray.remove(globalList['tagid'])
|
||||
else:
|
||||
print(globalList['tagName'] + " tag is not excluded for " + group['name'])
|
||||
|
||||
|
||||
return(tagArray)
|
||||
|
||||
def UpdateProtectionGroupIncludes(group, action):
|
||||
globalList = globals()
|
||||
isIncluded = False
|
||||
|
||||
tagArray = []
|
||||
|
||||
if "vmTagIds" in group:
|
||||
for index in group['vmTagIds']:
|
||||
for tag in index:
|
||||
tagArray.append(tag)
|
||||
if tag == globalList['tagid']:
|
||||
isIncluded = True
|
||||
|
||||
# Add the tag to the inclusion
|
||||
if action == 'add':
|
||||
if isIncluded == True:
|
||||
print(globalList['tagName'] + " tag is already included for " + group['name'])
|
||||
else:
|
||||
print("Adding inclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
|
||||
tagArray.append(globalList['tagid'])
|
||||
|
||||
# Remove the tag from the inclusion
|
||||
elif action == 'remove':
|
||||
if isIncluded == True:
|
||||
print("Removing inclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
|
||||
tagArray.remove(globalList['tagid'])
|
||||
else:
|
||||
print(globalList['tagName'] + " tag is not included for " + group['name'])
|
||||
|
||||
return(tagArray)
|
||||
|
||||
|
||||
# Begin Main
|
||||
globalList = globals()
|
||||
|
||||
sourceFound = False
|
||||
vCenter = None
|
||||
vCenterID = None
|
||||
tagIds = []
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
if args.help:
|
||||
PrintHelp()
|
||||
exit(1)
|
||||
|
||||
# Validate arguments & assign variables if necessary
|
||||
if not args.cluster:
|
||||
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
|
||||
else:
|
||||
globalList['tagName'] = args.tag
|
||||
|
||||
if not args.vcenter:
|
||||
sys.exit("Error: Specify a vCenter with -v parameter")
|
||||
|
||||
if not args.tag:
|
||||
sys.exit("Error: Specify a tag with the -t parameter.")
|
||||
|
||||
if not args.action:
|
||||
sys.exit("Error: Specify an action [add|remove] with -a parameter.")
|
||||
elif args.action != 'add' and args.action != 'remove':
|
||||
sys.exit("Error: Use only 'add' or 'remove' with -a parameter.")
|
||||
|
||||
if not args.list:
|
||||
sys.exit("Error: Specify a list [include|exclude] with -l parameter.")
|
||||
elif args.list != 'exclude' and args.list != 'include':
|
||||
sys.exit("Error: Use only 'include' or 'exclude' with -l parameter.")
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
cluster = cohesity.API(args.cluster)
|
||||
authToken = cluster.GetAuthToken()
|
||||
cluster.UpdateHeaders(authToken['accessToken'])
|
||||
|
||||
# Locate the correct vCenter and capture the id, this will be useful to validate child objects
|
||||
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
|
||||
for source in vmSources:
|
||||
if source['protectionSource']['name'] == args.vcenter:
|
||||
sourceFound = True
|
||||
vCenter = source
|
||||
vCenterID = source['protectionSource']['id']
|
||||
break
|
||||
|
||||
# The vCenter we are looking for was not in the list of kVMware environments, stop processing.
|
||||
if sourceFound == False:
|
||||
sys.exit("Error: " + args.vcenter + " is not registered to " + args.cluster)
|
||||
|
||||
# Look up the tag id based on the tagName and set a global variable when found
|
||||
# IMPROVEMENT TO GET AWAY FROM GLOBAL VAR
|
||||
GetTagID(vCenter)
|
||||
|
||||
if globalList['tagid'] is None:
|
||||
sys.exit("Error: The tag '" + args.tag + "' does not exist in " + vCenter['protectionSource']['name'])
|
||||
|
||||
if args.group:
|
||||
# Replace '@' symbol with '%40' for URL encoding in the REST API
|
||||
protectionGroup = args.group.replace("@","%40")
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + protectionGroup)
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
for job in uniqueJobs:
|
||||
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
sys.exit(job['name'] + " does not belong to source " + vCenter['protectionSource']['name'])
|
||||
else:
|
||||
if 'isDeleted' in job:
|
||||
continue
|
||||
|
||||
if (args.list == 'exclude'):
|
||||
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
|
||||
if "excludeVmTagIds" in job:
|
||||
job['excludeVmTagIds'][0] = excludeTagIds
|
||||
else:
|
||||
job.update({"excludeVmTagIds": [ excludeTagIds ]})
|
||||
else:
|
||||
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
|
||||
if "vmTagIds" in job:
|
||||
job['vmTagIds'][0] = includeTagIds
|
||||
else:
|
||||
job.update({"includeVmTagIds": [ includeTagIds ]})
|
||||
|
||||
# Take acion on group(s)
|
||||
resp = cluster.UpdateVMProtectionJob(job)
|
||||
print(resp.content)
|
||||
|
||||
else:
|
||||
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
|
||||
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
|
||||
|
||||
for job in uniqueJobs:
|
||||
if "isPaused" in job:
|
||||
if job['isPaused'] == True:
|
||||
continue
|
||||
# If the job is marked for deletion, skip
|
||||
if 'isDeleted' in job:
|
||||
if job['isDeleted'] == True:
|
||||
continue
|
||||
|
||||
# If the job does not belong to this vCetner, skip
|
||||
if job['parentSourceId'] != vCenterID:
|
||||
continue
|
||||
else:
|
||||
if (args.list == 'exclude'):
|
||||
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
|
||||
|
||||
if "excludeVmTagIds" in job:
|
||||
job['excludeVmTagIds'][0] = excludeTagIds
|
||||
else:
|
||||
job.update({"excludeVmTagIds": [ excludeTagIds ]})
|
||||
|
||||
else:
|
||||
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
|
||||
|
||||
if "vmTagIds" in job:
|
||||
job['vmTagIds'][0] = includeTagIds
|
||||
else:
|
||||
job.update({"includeVmTagIds": [ includeTagIds ]})
|
||||
|
||||
# Take action on group(s)
|
||||
resp = cluster.UpdateVMProtectionJob(job)
|
||||
|
||||
|
||||
if args.group:
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 10})
|
||||
else:
|
||||
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 240})
|
||||
@@ -0,0 +1,171 @@
|
||||
# All entries must be in lower case, the scripts will normalize all hostnames to lowercase to match.
|
||||
---
|
||||
azureNPD01:
|
||||
id: 1
|
||||
jobs:
|
||||
- "none"
|
||||
vms:
|
||||
# INC0410358
|
||||
- "^itd\\S+pamac[0-9]+"
|
||||
- "^itd\\S+pamac\\S+[0-9]+"
|
||||
- "^vm-itd\\S+pamac[0-9]+"
|
||||
- "^vm-itd\\S+pamac\\S+[0-9]+"
|
||||
# INC0410366
|
||||
- "sntest"
|
||||
# INC0572225
|
||||
- "itdsosblscmtwt1"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
azurePRD01:
|
||||
id: 37
|
||||
jobs:
|
||||
- "none"
|
||||
vms:
|
||||
# INC0410358
|
||||
- "^itd\\S+pamac[0-9]+"
|
||||
- "^itd\\S+pamac\\S+[0-9]+"
|
||||
- "^vm-itd\\S+pamac[0-9]+"
|
||||
- "^vm-itd\\S+pamac\\S+[0-9]+"
|
||||
# INC0410366
|
||||
- "sntest"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
azureCARES01:
|
||||
id: 4
|
||||
jobs:
|
||||
- "none"
|
||||
vms:
|
||||
- "itdmltesting"
|
||||
# INC0410358
|
||||
- "^itd\\S+pamac[0-9]+"
|
||||
- "^itd\\S+pamac\\S+[0-9]+"
|
||||
- "^vm-itd\\S+pamac[0-9]+"
|
||||
- "^vm-itd\\S+pamac\\S+[0-9]+"
|
||||
# INC0410366
|
||||
- "sntest"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
azureINFRA01:
|
||||
id: 28
|
||||
jobs:
|
||||
# INC0410358
|
||||
- "Infra-Networking-PA@infra01"
|
||||
# INC0199104
|
||||
- "Infra-Azure-Networking-Infoblox@infra01"
|
||||
vms:
|
||||
# INC0410358
|
||||
- "^itd\\S+pamac[0-9]+"
|
||||
- "^itd\\S+pamac\\S+[0-9]+"
|
||||
- "itdinfrapanoramalab-prd-400"
|
||||
- "^vm-itd\\S+pamac[0-9]+"
|
||||
- "^vm-itd\\S+pamac\\S+[0-9]+"
|
||||
- "vm-itdinf01exmac01-prd-001"
|
||||
- "vm-itdinf01exmac02-prd-001"
|
||||
- "itdinf01exmac01-prd-001"
|
||||
- "itdinf01exmac02-prd-001"
|
||||
- "vm-itdinfra01sharedbackuppamac001-prd-001"
|
||||
- "vm-itdinfra01sharedbackuppamac002-prd-002"
|
||||
- "itdinfra01sharedbackuppamac001-prd-001"
|
||||
- "itdinfra01sharedbackuppamac002-prd-002"
|
||||
# INC0199104
|
||||
- "^vm-ns[0-9]+-prd-[0-9]+"
|
||||
- "ns13.ns.nd.gov"
|
||||
- "ns14.ns.nd.gov"
|
||||
# INC0422813, INC0422812
|
||||
- "(^vm?|^itd?)\\S+infra\\S+srx\\S+"
|
||||
# INC0422811
|
||||
- "itdnettestingsrx"
|
||||
# Documented exception, these are the cohesity servers themselves
|
||||
- "sa[0-9]*"
|
||||
# INC0410366
|
||||
- "sntest"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
azurePAAS01:
|
||||
id: 12226
|
||||
jobs:
|
||||
- "Infra-Networking-PA@NDIT-PaaS"
|
||||
vms:
|
||||
- "^vm-itd\\S+pamac[0-9]+"
|
||||
- "^vm-itd\\S+pamac\\S+us[0-9]+"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
itdvmvc2:
|
||||
id: 1
|
||||
jobs:
|
||||
- "ITD-POC-zmeier@itdvmvc2"
|
||||
vms:
|
||||
# INC0410366
|
||||
- "clone"
|
||||
- "delete"
|
||||
- "broken"
|
||||
- "itdzmtest"
|
||||
- "sntest"
|
||||
# RITM0413140
|
||||
- "^itdnessus[0-9]+"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
itdvmvc1:
|
||||
id: 3156
|
||||
jobs:
|
||||
# INC0428498
|
||||
- "itd-poc-zmeier@itdvmvc1"
|
||||
- "ITD-POC-zmeier@itdvmvc1"
|
||||
# INC0468249
|
||||
- "NDPERS-Applications@physical"
|
||||
sql:
|
||||
# DBA's are using RedGate on this server
|
||||
- "itdsql16p3.nd.gov"
|
||||
vms:
|
||||
# RITM0413140
|
||||
- "^itdnessus[0-9]+"
|
||||
# INC0199104
|
||||
- "infoblox-egmv1.ns.nd.gov"
|
||||
- "infoblox-gmv1.ns.nd.gov"
|
||||
- "vm-ns4.ndnic.com-prd-400"
|
||||
# INC0152611
|
||||
- "itdavayarec.nd.gov"
|
||||
- "itdbiscmsvra.nd.gov"
|
||||
- "itdbiscmsvrb.nd.gov"
|
||||
- "itdwfoaes4.nd.gov"
|
||||
# INC0410383
|
||||
- "itdcaldera1"
|
||||
- "itdcuckoo1"
|
||||
- "itdcape1"
|
||||
- "itdcape2"
|
||||
- "itdremnux"
|
||||
- "itdflarevm"
|
||||
# INC0488924
|
||||
- "itdremworkstation.nd.gov"
|
||||
- "itdremworkstation"
|
||||
# INC0410366
|
||||
- "sntest"
|
||||
- "clone"
|
||||
- "delete"
|
||||
- "broken"
|
||||
- "itdzmtest"
|
||||
# INC0488902 / 20250310 (vantis)
|
||||
- "admin_vm"
|
||||
- "controller0"
|
||||
- "controller1"
|
||||
- "controller2"
|
||||
- "worker0"
|
||||
- "worker1"
|
||||
- "worker2"
|
||||
- "worker3"
|
||||
- "security"
|
||||
- "bastion"
|
||||
- "admin"
|
||||
# INC0848221
|
||||
- "Windows Server 2025 Standard 24H2.6"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
itdvmvct1:
|
||||
id: 8557
|
||||
jobs:
|
||||
- "[a-z]*"
|
||||
vms:
|
||||
- "[a-z]*"
|
||||
- "sntest"
|
||||
templates:
|
||||
- "[a-z]*"
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/python
|
||||
import sys,argparse,json,time
|
||||
import time
|
||||
sys.path.insert(0, './classes/')
|
||||
|
||||
import cohesityAPI as cohesity
|
||||
|
||||
def GetArgs():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--source', '-s', type=str, action='store')
|
||||
parser.add_argument('--replica', '-r', type=str, action='store')
|
||||
parser.add_argument('--delete', '-del', type=str, action='store')
|
||||
return (parser.parse_args())
|
||||
|
||||
args = GetArgs()
|
||||
|
||||
# Connect to the Cohesity cluster
|
||||
source_cluster = cohesity.API(args.source)
|
||||
source_token = source_cluster.GetAuthToken()
|
||||
source_cluster.UpdateHeaders(source_token['accessToken'])
|
||||
source_jobs = source_cluster.GetRequest("/public/protectionJobs")
|
||||
|
||||
# Connect to the Replica cluster
|
||||
replica_cluster = cohesity.API(args.replica)
|
||||
replica_token = replica_cluster.GetAuthToken()
|
||||
replica_cluster.UpdateHeaders(replica_token['accessToken'])
|
||||
replica_jobs = replica_cluster.GetRequest("/public/protectionJobs")
|
||||
|
||||
deleted_on_source=[]
|
||||
deleted_on_remote=[]
|
||||
source_only=[]
|
||||
remote_only=[]
|
||||
|
||||
legal_holds_on_deleted_jobs=[]
|
||||
|
||||
for s_job in source_jobs:
|
||||
if 'isDeleted' in s_job:
|
||||
if s_job['isDeleted'] == True:
|
||||
deleted_on_source.append(s_job)
|
||||
|
||||
# Verify there aren't any orphaned snapshots due to legal holds
|
||||
p = s_job['policyId'].split(":")
|
||||
policy = str(p[0])+":"+str(p[1])+":"+str(s_job['id'])
|
||||
run_data = source_cluster.GetRequestV2("/data-protect/protection-groups/"+policy+"/runs?numRuns=1000")
|
||||
runs = run_data['runs']
|
||||
|
||||
for run in runs:
|
||||
if run['onLegalHold']:
|
||||
url = "https://{0}/protection/group/run/backup/{1}/{2}".format(args.source,run['protectionGroupId'],run['id'])
|
||||
legal_holds_on_deleted_jobs.append(url)
|
||||
|
||||
continue
|
||||
|
||||
is_on_both = False
|
||||
issource_only = False
|
||||
isremote_only = False
|
||||
|
||||
for r_job in replica_jobs:
|
||||
if s_job['name'] == r_job['name']:
|
||||
is_on_both = True
|
||||
|
||||
if is_on_both:
|
||||
continue
|
||||
|
||||
else:
|
||||
source_only.append(s_job)
|
||||
|
||||
for r_job in replica_jobs:
|
||||
is_on_both = False
|
||||
issource_only = False
|
||||
isremote_only = False
|
||||
|
||||
if 'isDeleted' in r_job:
|
||||
if r_job['isDeleted'] == True:
|
||||
deleted_on_replica.append(r_job)
|
||||
|
||||
# Verify there aren't any orphaned snapshots due to legal holds
|
||||
p = r_job['policyId'].split(":")
|
||||
policy = str(p[0])+":"+str(p[1])+":"+str(r_job['id'])
|
||||
run_data = replica_cluster.GetRequestV2("/data-protect/protection-groups/"+policy+"/runs?numRuns=1000")
|
||||
runs = run_data['runs']
|
||||
|
||||
for run in runs:
|
||||
if run['onLegalHold']:
|
||||
url = "https://{0}/protection/group/run/backup/{1}/{2}".format(args.replica,run['protectionGroupId'],run['id'])
|
||||
legal_holds_on_deleted_jobs.append(url)
|
||||
|
||||
continue
|
||||
|
||||
for s_job in source_jobs:
|
||||
if s_job['name'] == r_job['name']:
|
||||
is_on_both = True
|
||||
|
||||
if is_on_both:
|
||||
continue
|
||||
|
||||
else:
|
||||
remote_only.append(r_job)
|
||||
|
||||
print("\nSource Only:")
|
||||
for src_val in source_only:
|
||||
#resp = source_cluster.DeleteJob("/public/protectionJobs/"+str(value['id']))
|
||||
#print(resp.content)
|
||||
print(src_val['name'])
|
||||
|
||||
print("\nRemote Only:")
|
||||
for repl_val in remote_only:
|
||||
print(repl_val['name'])
|
||||
|
||||
if (args.delete):
|
||||
print("Deleting " + repl_val['name'] + " on replica")
|
||||
resp = replica_cluster.DeleteJob("/public/protectionJobs/"+str(value['id']))
|
||||
print(resp.content)
|
||||
|
||||
|
||||
print("\nDeleted in Source:")
|
||||
for del_val in deleted_on_source:
|
||||
print(del_val['name'])
|
||||
|
||||
print("\nDeleted on Replica:")
|
||||
for del_val in deleted_on_source:
|
||||
print(del_val['name'])
|
||||
|
||||
print("\nDeleted Jobs with legal holds on snapshots:")
|
||||
for lh in legal_holds_on_deleted_jobs:
|
||||
print(json.dumps(lh,indent=2))
|
||||
@@ -0,0 +1,139 @@
|
||||
#
|
||||
# Module manifest for module 'PSGet_ITDAzureRM'
|
||||
#
|
||||
# Generated by: zmeier
|
||||
#
|
||||
# Generated on: 2/18/2019
|
||||
#
|
||||
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'ITDAzureRM.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '0.4.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = 'dec17c9a-f0ec-439f-ac81-bc24a4815115'
|
||||
|
||||
# Author of this module
|
||||
Author = 'zmeier','cdfelchle'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'Unknown'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) 2019 zmeier. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Functions for Azure Resource Manager administrative tasks'
|
||||
|
||||
# Minimum version of the Windows PowerShell engine required by this module
|
||||
# PowerShellVersion = ''
|
||||
|
||||
# Name of the Windows PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the Windows PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# CLRVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = 'Set-ITDAzureRMResourceGroupAssignment', 'Get-ITDAzureRMIPs',
|
||||
'Set-ITDAzureRMResourceGroupTags',
|
||||
'Set-ITDAzureRMResourceGroupTagsRefresh',
|
||||
'New-ITDAzureRMPolicyDefinition',
|
||||
'Find-ITDAzureRMUntaggedResources', 'New-ITDAzureRMVM',
|
||||
'Remove-ITDAzureRMResourceGroupTags', 'Add-ITDAzureRMNewDataDisk',
|
||||
'New-ITDAzureVM', 'New-ITDAzureRMResourceGroup', 'Find-ITDAzureRMVM',
|
||||
'Set-ITDAzureRmVMBackup'
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = '*'
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = '*'
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = False
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = ''
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
#20190204 - 2nd comment, this one in master branch
|
||||
#7th comment
|
||||
|
||||
# copy disk example
|
||||
<#
|
||||
set-azurermcontext -Subscription npd01
|
||||
$disk = Get-AzureRmDisk -ResourceGroupName rg-doh-intranetconnections-tst -DiskName vm-itddohict1-app-tst-001
|
||||
set-azurermcontext -Subscription prd01
|
||||
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
|
||||
New-AzureRmDisk -ResourceGroupName rg-doh-intranetconnections-prd -DiskName vm-itddohicp1-app-prd-001 -Disk $newdisk
|
||||
#>
|
||||
|
||||
|
||||
|
||||
|
||||
$computername="itdadfsldt1"
|
||||
#Provide the subscription Id
|
||||
$subscriptionId = '76297098-764c-43de-8525-c9fda1b237be'
|
||||
|
||||
#Provide the name of your resource group
|
||||
$resourceGroupName ='rg-infra-adfs-tst'
|
||||
|
||||
Set-AzureRmContext -Subscription infra01
|
||||
$disk = Get-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName "dev_sda-vm_itdadfsldt1_tst"
|
||||
Set-AzureRmContext -Subscription npd01
|
||||
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
|
||||
New-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName vm-$computername-os-tst -Disk $newdisk
|
||||
|
||||
#Provide the name of the OS disk that will be created using the snapshot
|
||||
$osDiskName = "vm-$computername-os-tst"
|
||||
|
||||
#Provide the name of the virtual machine
|
||||
$virtualMachineName = "vm-$computername-tst"
|
||||
|
||||
$nicName = "nic-$computername-tst-101"
|
||||
|
||||
#Provide the size of the virtual machine
|
||||
#e.g. Standard_DS3
|
||||
#Get all the vm sizes in a region using below script:
|
||||
#e.g. Get-AzureRmVMSize -Location westus
|
||||
$virtualMachineSize = 'Standard_A1'
|
||||
|
||||
#Set the context to the subscription Id where Managed Disk will be created
|
||||
#Select-AzureRmSubscription -SubscriptionId $SubscriptionId
|
||||
Set-AzureRmContext -Subscription npd01
|
||||
<#
|
||||
$resourceGroupName="rg-doh-intranetconnections-prd"
|
||||
$osDiskName="vm-itddohicp1-os-prd"
|
||||
$virtualMachineName="vm-itddohicp1-prd"
|
||||
$virtualMachineSize="Standard_B2ms"
|
||||
$nicName="nic-itddohicp1-prd"
|
||||
#>
|
||||
|
||||
$disk = Get-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName $osDiskName
|
||||
|
||||
#Initialize virtual machine configuration
|
||||
$VirtualMachine = New-AzureRmVMConfig -VMName $virtualMachineName -VMSize $virtualMachineSize
|
||||
|
||||
#Use the Managed Disk Resource Id to attach it to the virtual machine. Please change the OS type to linux if OS disk has linux OS
|
||||
$VirtualMachine = Set-AzureRmVMOSDisk -VM $VirtualMachine -ManagedDiskId $disk.Id -CreateOption Attach -Linux
|
||||
|
||||
$nic = Get-AzureRmNetworkInterface -ResourceGroupName $resourcegroupname -Name $nicName
|
||||
|
||||
$VirtualMachine = Add-AzureRmVMNetworkInterface -VM $VirtualMachine -Id $nic.Id
|
||||
|
||||
#Create the virtual machine with Managed Disk
|
||||
New-AzureRmVM -VM $VirtualMachine -ResourceGroupName $resourceGroupName -Location centralus
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Set-AzureRmContext -Subscription infra01
|
||||
$disks = Get-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -ov npddisks
|
||||
Set-AzureRmContext -Subscription npd01
|
||||
ForEach($d in $disks)
|
||||
{
|
||||
$newdisk=$null
|
||||
$computername=$null
|
||||
|
||||
|
||||
$computername = $d.name.split('_')[2]
|
||||
If($d.name -like "*sda*")
|
||||
{
|
||||
$newname = "vm-$computername-os-tst"
|
||||
}
|
||||
If($d.name -like "*sdb*")
|
||||
{
|
||||
$newname = "vm-$computername-app-tst-101"
|
||||
$newdisk = New-AzureRmDiskConfig -SourceResourceId $d.Id -Location centralus -createoption Copy
|
||||
New-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName $NewName -Disk $newdisk
|
||||
}
|
||||
}
|
||||
<#
|
||||
$disk = Get-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName dev_sdb-vm_itdadfsintldt1_tst
|
||||
set-azurermcontext -Subscription npd01
|
||||
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
|
||||
New-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName vm-itdadfsldt1-app-tst-001 -Disk $newdisk
|
||||
#>
|
||||
@@ -0,0 +1,20 @@
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: zm test
|
||||
hosts: itdscmt1.nd.gov
|
||||
tasks:
|
||||
- name: run command
|
||||
win_command: netstat
|
||||
- name: Run PowerShell script with parameter
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
New-Item D:\{{ ComputerName }}.txt
|
||||
Write-Output "{{ ComputerName }}"
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
name: 'ITD.All-General'
|
||||
|
||||
variables:
|
||||
major: 1
|
||||
minor: 0
|
||||
patch: $(Build.BuildID)
|
||||
buildVer: $(major).$(minor).$(Build.BuildID)
|
||||
|
||||
pool: itdwinautop1
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: Build
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.All-General.nuspec'
|
||||
versioningScheme: byEnvVar
|
||||
versionEnvVar: buildVer
|
||||
buildProperties: 'VERSIONHERE=$(buildVer)'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'NuGetPackage'
|
||||
publishLocation: 'Container'
|
||||
- stage: Deploy
|
||||
jobs:
|
||||
- job: Deploy
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'NuGetPackage'
|
||||
itemPattern: '**'
|
||||
targetPath: '$(Pipeline.Workspace)'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'push'
|
||||
packagesToPush: '$(Pipeline.Workspace)/ITD.All-General.$(major).$(minor).$(Build.BuildID).nupkg'
|
||||
nuGetFeedType: external
|
||||
publishFeedCredentials: 'ITD_PwshGallery'
|
||||
@@ -0,0 +1,17 @@
|
||||
$buildVersion = $env:BUILDVER
|
||||
$moduleName = 'ITD.All-General'
|
||||
|
||||
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
|
||||
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
|
||||
|
||||
## Update build version in manifest
|
||||
$manifestContent = Get-Content -Path $manifestPath -Raw
|
||||
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
|
||||
|
||||
## Update functions to export in manifest
|
||||
Import-Module $modulePath
|
||||
$funcStrings = (Get-Module ITD.All-General).ExportedCommands.Values.Name
|
||||
$funcStrings = "'$($funcStrings -join "','")'"
|
||||
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
|
||||
|
||||
$manifestContent | Set-Content -Path $manifestPath
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>ITD.All-General</id>
|
||||
<version>$VERSIONHERE$</version>
|
||||
<authors>Zack Meier</authors>
|
||||
<description>QoL functions that don't fit into another module</description>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\.git\**;**\Build\**" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,11 @@
|
||||
@{
|
||||
RootModule = 'ITD.All-General.psm1'
|
||||
ModuleVersion = '<ModuleVersion>'
|
||||
GUID = '9c9219d5-d7be-49e5-8448-49c50df94eda'
|
||||
Author = 'Zack Meier'
|
||||
CompanyName = 'State of North Dakota'
|
||||
Description = "QoL functions that don't fit into another module"
|
||||
PowerShellVersion = '5.1'
|
||||
CompatiblePSEditions = 'Desktop', 'Core'
|
||||
FunctionsToExport = @(<FunctionsToExport>)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#Get public and private function definition files.
|
||||
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
|
||||
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
|
||||
|
||||
#Dot source the files
|
||||
Foreach($import in @($Public + $Private))
|
||||
{
|
||||
Try
|
||||
{
|
||||
. $import.fullname
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Write-Error -Message "Failed to import function $($import.fullname): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Here I might...
|
||||
# Read in or create an initial config file and variable
|
||||
# Export Public functions ($Public.BaseName) for WIP modules
|
||||
# Set variables visible to the module and its functions only
|
||||
|
||||
Export-ModuleMember -Function $Public.Basename
|
||||
@@ -0,0 +1,32 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Short description
|
||||
.DESCRIPTION
|
||||
Long description
|
||||
.EXAMPLE
|
||||
$servers=@"
|
||||
server1.xyz.com
|
||||
servers2.xyz.com
|
||||
@"
|
||||
|
||||
$servers = ConvertTo-Array -MultiLineString $servers
|
||||
.EXAMPLE
|
||||
Another example of how to use this cmdlet
|
||||
#>
|
||||
function ConvertTo-Array {
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[string]
|
||||
$MultiLineString
|
||||
)
|
||||
|
||||
Begin {
|
||||
}
|
||||
Process {
|
||||
$result = @($MultiLineString -split '[\r\n]+')
|
||||
}
|
||||
End {
|
||||
return $result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
Test-MyTestFunction -Verbose
|
||||
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
|
||||
#>
|
||||
|
||||
function Get-SslCertificate {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$DNSName
|
||||
)
|
||||
|
||||
process {
|
||||
Write-Verbose "Checking certificate for $DNSName"
|
||||
|
||||
$tcp = [Net.Sockets.TcpClient]::new($DNSName, 443)
|
||||
$ssl = [Net.Security.SslStream]::new(
|
||||
$tcp.GetStream(),
|
||||
$false,
|
||||
{ $true }
|
||||
)
|
||||
|
||||
$ssl.AuthenticateAsClient($DNSName)
|
||||
|
||||
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
|
||||
$ssl.RemoteCertificate
|
||||
)
|
||||
|
||||
$ssl.Dispose()
|
||||
$tcp.Dispose()
|
||||
|
||||
$cert
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Verify AD credentials are valid
|
||||
.DESCRIPTION
|
||||
Verify AD credentials are valid ##
|
||||
.EXAMPLE
|
||||
Test-ADCredential -Credential <PSCredential>
|
||||
#>
|
||||
function Test-ADCredential {
|
||||
[CmdletBinding()]#
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
Begin {
|
||||
|
||||
}
|
||||
Process {
|
||||
If ($Credential -eq $null) {
|
||||
Write-Warning "Credentials empty"
|
||||
$status = $true
|
||||
}
|
||||
Else {
|
||||
$username = $Credential.username
|
||||
$password = $Credential.GetNetworkCredential().password
|
||||
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
|
||||
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
|
||||
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('domain')
|
||||
|
||||
#($ValidateCredential = ) | Out-Null
|
||||
|
||||
If ($DS.ValidateCredentials($UserName, $Password) -eq $false) {
|
||||
$password = $null
|
||||
Write-Error "Invalid credentials or locked account."
|
||||
$status = $false
|
||||
}
|
||||
Else {
|
||||
$status = $true
|
||||
}
|
||||
|
||||
$password = $null
|
||||
}
|
||||
}
|
||||
End {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Removes old versions of ITD modules
|
||||
.DESCRIPTION
|
||||
Long description
|
||||
.EXAMPLE
|
||||
Example of how to use this cmdlet
|
||||
.EXAMPLE
|
||||
Another example of how to use this cmdlet
|
||||
.INPUTS
|
||||
Inputs to this cmdlet (if any)
|
||||
.OUTPUTS
|
||||
Output from this cmdlet (if any)
|
||||
.NOTES
|
||||
General notes
|
||||
.COMPONENT
|
||||
The component this cmdlet belongs to
|
||||
.ROLE
|
||||
The role this cmdlet belongs to
|
||||
.FUNCTIONALITY
|
||||
The functionality that best describes this cmdlet
|
||||
#>
|
||||
function Uninstall-ITDModuleOldVersion {
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
)
|
||||
|
||||
begin {
|
||||
}
|
||||
|
||||
process {
|
||||
$InstalledModules = Get-InstalledModule -Name ITD.*
|
||||
|
||||
try {
|
||||
$InstalledModules | ForEach-Object {
|
||||
$CurrentVersion = $_.Version
|
||||
Get-InstalledModule -Name $_.Name -AllVersions | Where-Object -Property Version -LT -Value $CurrentVersion
|
||||
} | Uninstall-Module -Verbose
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
Test-MyTestFunction -Verbose
|
||||
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
|
||||
#>
|
||||
|
||||
function Update-ITDModule {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
$InstalledModules = Get-InstalledModule -Name ITD.*
|
||||
Update-Module -Name $InstalledModules.Name
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
@@ -0,0 +1,49 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
name: 'ITD.ITD-WindowsServer.FileManagement'
|
||||
|
||||
variables:
|
||||
major: 0
|
||||
minor: 7
|
||||
patch: $(Build.BuildID)
|
||||
buildVer: $(major).$(minor).$(Build.BuildID)
|
||||
|
||||
pool: itdwinautop1
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: Build
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.FileManagement.nuspec'
|
||||
versioningScheme: byEnvVar
|
||||
versionEnvVar: buildVer
|
||||
buildProperties: 'VERSIONHERE=$(buildVer)'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'NuGetPackage'
|
||||
publishLocation: 'Container'
|
||||
- stage: Deploy
|
||||
jobs:
|
||||
- job: Deploy
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'NuGetPackage'
|
||||
itemPattern: '**'
|
||||
targetPath: '$(Pipeline.Workspace)'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'push'
|
||||
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.FileManagement.$(major).$(minor).$(Build.BuildID).nupkg'
|
||||
nuGetFeedType: external
|
||||
publishFeedCredentials: 'ITD_PwshGallery'
|
||||
@@ -0,0 +1,17 @@
|
||||
$buildVersion = $env:BUILDVER
|
||||
$moduleName = 'ITD.ITD-WindowsServer.FileManagement'
|
||||
|
||||
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
|
||||
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
|
||||
|
||||
## Update build version in manifest
|
||||
$manifestContent = Get-Content -Path $manifestPath -Raw
|
||||
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
|
||||
|
||||
## Update functions to export in manifest
|
||||
Import-Module $modulePath
|
||||
$funcStrings = (Get-Module ITD.ITD-WindowsServer.FileManagement).ExportedCommands.Values.Name
|
||||
$funcStrings = "'$($funcStrings -join "','")'"
|
||||
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
|
||||
|
||||
$manifestContent | Set-Content -Path $manifestPath
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"ComputerName": "itdxyz.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"emailA@nd.gov",
|
||||
"emailB@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs\\LogFiles",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 90,
|
||||
"Recursive": true
|
||||
},
|
||||
{
|
||||
"Path": "C:\\temp",
|
||||
"Extension": "txt",
|
||||
"DaysToKeep": 30,
|
||||
"Recursive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"ComputerName": "itddohslimst2.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 120,
|
||||
"Recursive": true
|
||||
},
|
||||
{
|
||||
"Path": "C:\\Program Files\\STARLIMS\\STARLIMStst\\Log",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 120,
|
||||
"Recursive": true
|
||||
},
|
||||
{
|
||||
"Path": "E:\\Program Files\\STARLIMS\\STARLIMSdev\\Log",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 120,
|
||||
"Recursive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ComputerName": "itdernappt01.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 90,
|
||||
"Recursive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ComputerName": "itdernappu01.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 90,
|
||||
"Recursive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"ComputerName": "itdscmt1.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 90,
|
||||
"Recursive": true
|
||||
},
|
||||
{
|
||||
"Path": "C:\\temp",
|
||||
"Extension": "txt",
|
||||
"DaysToKeep": 30,
|
||||
"Recursive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ComputerName": "itdvmvc1script.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs\\LogFiles",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 90,
|
||||
"Recursive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ComputerName": "itdwinautot1.nd.gov",
|
||||
"NotifyEmail": [
|
||||
"zmeier@nd.gov"
|
||||
],
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\temp",
|
||||
"Extension": "txt",
|
||||
"DaysToKeep": 30,
|
||||
"Recursive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>ITD.ITD-WindowsServer.FileManagement</id>
|
||||
<version>$VERSIONHERE$</version>
|
||||
<authors>Zack Meier</authors>
|
||||
<description>Functions for Windows Server file management</description>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\.git\**;**\Build\**" />
|
||||
</files>
|
||||
</package>
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
#
|
||||
# Module manifest for module 'ITD.ITD-WindowsServer.FileManagement'
|
||||
#
|
||||
# Generated by: zmeier
|
||||
#
|
||||
# Generated on: 6/14/2022
|
||||
#
|
||||
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'ITD.ITD-WindowsServer.FileManagement.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '<ModuleVersion>'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = 'Desktop', 'Core'
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '85e7676b-f0e3-4908-aafa-25c9606ca8b7'
|
||||
|
||||
# Author of this module
|
||||
Author = 'zmeier'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'State of North Dakota'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) zmeier. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Functions for Windows Server file management'
|
||||
|
||||
# Minimum version of the PowerShell engine required by this module
|
||||
# PowerShellVersion = ''
|
||||
|
||||
# Name of the PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# ClrVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @(<FunctionsToExport>)
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = $false
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = @()
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#Get public and private function definition files.
|
||||
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
|
||||
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
|
||||
|
||||
#Dot source the files
|
||||
Foreach($import in @($Public + $Private))
|
||||
{
|
||||
Try
|
||||
{
|
||||
. $import.fullname
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Write-Error -Message "Failed to import function $($import.fullname): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Here I might...
|
||||
# Read in or create an initial config file and variable
|
||||
# Export Public functions ($Public.BaseName) for WIP modules
|
||||
# Set variables visible to the module and its functions only
|
||||
|
||||
Export-ModuleMember -Function $Public.Basename
|
||||
@@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"ComputerName": "itdscmt1.nd.gov",
|
||||
"NotifyEmail": "zmeier@nd.gov",
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\inetpub\\logs\\LogFiles",
|
||||
"DaysToKeep": 120,
|
||||
"Extension": "log",
|
||||
"Recursive": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ComputerName": "itdzmtest555.nd.gov",
|
||||
"NotifyEmail": "zmeier@nd.gov",
|
||||
"Directory": [
|
||||
{
|
||||
"Path": "C:\\windows\\temp",
|
||||
"DaysToKeep": 99,
|
||||
"Extension": "log",
|
||||
"Recursive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
- ComputerName: itdscmt1.nd.gov
|
||||
Directory:
|
||||
- Path: C:\windows\temp
|
||||
DaysToKeep: 30
|
||||
Extension: txt
|
||||
Recursive: false
|
||||
- ComputerName: itdsccmp2.nd.gov
|
||||
Directory:
|
||||
- Path: C:\windows\temp
|
||||
DaysToKeep: 15
|
||||
Extension: txt
|
||||
Recursive: false
|
||||
- Path: C:\inetpub\logs
|
||||
DaysToKeep: 120
|
||||
Extension: txt
|
||||
Recursive: false
|
||||
- ComputerName: itdzmtest555.nd.gov
|
||||
Directory:
|
||||
- Path: "C:\windows\temp",
|
||||
DaysToKeep": 99,
|
||||
Extension": "log",
|
||||
Recursive": false
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"Path": "C:\\temp",
|
||||
"Extension": "log",
|
||||
"DaysToKeep": 30,
|
||||
"Recursive": true
|
||||
},
|
||||
{
|
||||
"Path": "C:\\temp2",
|
||||
"Extension": "txt",
|
||||
"DaysToKeep": 45,
|
||||
"Recursive": false
|
||||
}
|
||||
]
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Script will discover all files that are considered "expired".
|
||||
.DESCRIPTION
|
||||
Script will discover all files that are considered "expired" based on the parameters in the ./Helpers/ITDExpiredFiles.json files in the ITD-WindowsServer module
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
|
||||
.EXAMPLE
|
||||
Get-ITDExpiredFiles
|
||||
.EXAMPLE
|
||||
Get-ITDExpiredFiles -Credential $PrvCred
|
||||
.EXAMPLE
|
||||
Get-ITDExpiredFiles -ComputerName itdxyz.nd.gov -Credential $PrvCred
|
||||
#>
|
||||
|
||||
function Get-ITDExpiredFiles {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
Begin {
|
||||
Write-Verbose -Message "Load json files into memory"
|
||||
$JsonFiles = Get-ChildItem -Path "$PSScriptRoot\..\Helpers\*.json" | Where-Object Name -NE ^template.json
|
||||
$MachineInfo = ForEach ($file in $JsonFiles) {
|
||||
Get-Content -Path $file.FullName | ConvertFrom-Json
|
||||
}
|
||||
|
||||
If ($PSBoundParameters.ContainsKey('ComputerName')) {
|
||||
Write-Verbose -Message "ComputerName found"
|
||||
$MachineInfo = $MachineInfo | Where-Object ComputerName -EQ $ComputerName
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
Write-Verbose -Message "Prep discovery function"
|
||||
$GetExpiredFilesFunc = {
|
||||
Write-Verbose -Message ($env:COMPUTERNAME + " " + $args[0] + " " + $args[1] + " " + $args[2])
|
||||
|
||||
$GetChildItemParams = @{
|
||||
Path = $args[0];
|
||||
Filter = $args[1];
|
||||
Recurse = $args[2];
|
||||
}
|
||||
|
||||
$FilesFound = (Get-ChildItem @GetChildItemParams | Where-Object LastWriteTime -LT ((Get-Date).AddDays(-$args[3])))
|
||||
Write-Output $FilesFound
|
||||
}
|
||||
|
||||
$GetITDExpiredFilesResult = [System.Collections.ArrayList]@()
|
||||
ForEach ($Server in $MachineInfo) {
|
||||
Write-Verbose -Message ("Start " + $Server.ComputerName)
|
||||
|
||||
Write-Verbose -Message "Ping test before any Invoke-Command"
|
||||
If ((Test-NetConnection -ComputerName $Server.ComputerName).PingSucceeded) {
|
||||
ForEach ($Directory in $Server.Directory) {
|
||||
Write-Verbose -Message ("Start " + $server.ComputerName + " " + $Directory.Path)
|
||||
$FilesFoundOnServer = $null
|
||||
$InvokeCommandParams = $null
|
||||
$InvokeResult = $null
|
||||
|
||||
$InvokeCommandParams = @{
|
||||
ComputerName = $Server.ComputerName;
|
||||
Credential = $Credential;
|
||||
ScriptBlock = $GetExpiredFilesFunc;
|
||||
ArgumentList = @($Directory.Path, ("*" + $Directory.Extension), $Directory.Recursive, $Directory.DaysToKeep);
|
||||
}
|
||||
$FilesFoundOnServer = Invoke-Command @InvokeCommandParams
|
||||
Write-Output $FilesFoundOnServer
|
||||
#$null = $GetITDExpiredFilesResult.Add($FilesFoundOnServer)
|
||||
Write-Verbose -Message ("End " + $server.ComputerName + " " + $Directory.Path)
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Error -Message ($Server.ComputerName + " ping test failed, generate ticket someday.")
|
||||
}
|
||||
}
|
||||
Write-Verbose -Message ("End " + $server.ComputerName)
|
||||
}
|
||||
End {
|
||||
#Write-Output $GetITDExpiredFilesResult
|
||||
Write-Verbose -Message "End Get-ITDExpiredFiles"
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
function Remove-ITDExpiredFiles {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[switch]
|
||||
$WhatIf,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
Begin {
|
||||
Write-Verbose -Message "Start Remove-ITDExpiredFiles"
|
||||
}
|
||||
Process {
|
||||
$FilesRemovedSuccess = @()
|
||||
$FilesRemovedFailure = @()
|
||||
$GetITDExpiredFilesAutoParams += @{}
|
||||
|
||||
If ($PSBoundParameters.ContainsKey('ComputerName')) {
|
||||
Write-Verbose -Message "ComputerName parameter found"
|
||||
$GetITDExpiredFilesParams += @{
|
||||
ComputerName = $ComputerName;
|
||||
}
|
||||
}
|
||||
|
||||
If ($PSBoundParameters.ContainsKey('Credential')) {
|
||||
Write-Verbose -Message "Credential parameter found"
|
||||
$GetITDExpiredFilesParams += @{
|
||||
Credential = $Credential;
|
||||
}
|
||||
}
|
||||
$FilesToRemove = Get-ITDExpiredFiles @GetITDExpiredFilesParams
|
||||
Write-Verbose -Message ("Found " + $FilesToRemove.count + " expired files to remove")
|
||||
|
||||
ForEach ($File in $FilesToRemove) {
|
||||
Write-Verbose -Message ("Start~" + $File.PSComputerName + "~" + $File.FullName )
|
||||
$InvokeCommandParams = @{
|
||||
ComputerName = $File.PSComputerName;
|
||||
Credential = $Credential;
|
||||
ErrorAction = 'Stop';
|
||||
ArgumentList = @($File.FullName);
|
||||
ScriptBlock = { Get-Item -Path $args[0] | Remove-Item }
|
||||
}
|
||||
|
||||
switch ($WhatIf) {
|
||||
$true {
|
||||
try {
|
||||
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " removed")
|
||||
Write-Host -Message ($Server.ComputerName + " -- " + 'What if: Performing the operation "Remove File" on target ' + $File.FullName)
|
||||
# log success
|
||||
$FilesRemovedSuccess += [PSCustomObject]@{
|
||||
ComputerName = $File.PSComputerName;
|
||||
Name = $File.Fullname;
|
||||
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
|
||||
}
|
||||
Write-Output $File
|
||||
}
|
||||
catch {
|
||||
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " failure")
|
||||
# log failure
|
||||
$FilesRemovedFailure += [PSCustomObject]@{
|
||||
ComputerName = $File.PSComputerName;
|
||||
Name = $File.Fullname;
|
||||
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
|
||||
}
|
||||
}
|
||||
}
|
||||
Default {
|
||||
try {
|
||||
Invoke-Command @InvokeCommandParams
|
||||
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " removed")
|
||||
# log success
|
||||
$FilesRemovedSuccess += [PSCustomObject]@{
|
||||
ComputerName = $File.PSComputerName;
|
||||
Name = $File.Fullname;
|
||||
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
|
||||
}
|
||||
Write-Output $File
|
||||
}
|
||||
catch {
|
||||
Write-Verbose -Message ("Start~" + $File.PSComputerName + "~" + $File.FullName + " failure")
|
||||
# log failure
|
||||
$FilesRemovedFailure += [PSCustomObject]@{
|
||||
ComputerName = $File.PSComputerName;
|
||||
Name = $File.Fullname;
|
||||
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Verbose -Message ("End~" + $File.PSComputerName + "~" + $File.FullName )
|
||||
}
|
||||
}
|
||||
End {
|
||||
Write-Verbose -Message "End Remove-ITDExpiredFilesAuto"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
@@ -0,0 +1,49 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
name: 'ITD.ITD-WindowsServer.General'
|
||||
|
||||
variables:
|
||||
major: 0
|
||||
minor: 7
|
||||
patch: $(Build.BuildID)
|
||||
buildVer: $(major).$(minor).$(Build.BuildID)
|
||||
|
||||
pool: itdwinautop1
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: Build
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.General.nuspec'
|
||||
versioningScheme: byEnvVar
|
||||
versionEnvVar: buildVer
|
||||
buildProperties: 'VERSIONHERE=$(buildVer)'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'NuGetPackage'
|
||||
publishLocation: 'Container'
|
||||
- stage: Deploy
|
||||
jobs:
|
||||
- job: Deploy
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'NuGetPackage'
|
||||
itemPattern: '**'
|
||||
targetPath: '$(Pipeline.Workspace)'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'push'
|
||||
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.General.$(major).$(minor).$(Build.BuildID).nupkg'
|
||||
nuGetFeedType: external
|
||||
publishFeedCredentials: 'ITD_PwshGallery'
|
||||
@@ -0,0 +1,17 @@
|
||||
$buildVersion = $env:BUILDVER
|
||||
$moduleName = 'ITD.ITD-WindowsServer.General'
|
||||
|
||||
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
|
||||
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
|
||||
|
||||
## Update build version in manifest
|
||||
$manifestContent = Get-Content -Path $manifestPath -Raw
|
||||
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
|
||||
|
||||
## Update functions to export in manifest
|
||||
Import-Module $modulePath
|
||||
$funcStrings = (Get-Module ITD.ITD-WindowsServer.General).ExportedCommands.Values.Name
|
||||
$funcStrings = "'$($funcStrings -join "','")'"
|
||||
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
|
||||
|
||||
$manifestContent | Set-Content -Path $manifestPath
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
Test-MyTestFunction -Verbose
|
||||
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
|
||||
#>
|
||||
|
||||
function New-ITDWindowsVM {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet('VMware', 'Azure')]
|
||||
[string]
|
||||
$Platform,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]
|
||||
$CPU,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]
|
||||
$MemoryGB,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]
|
||||
$DiskOS,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]
|
||||
$DiskSwap,
|
||||
|
||||
[int]
|
||||
$DiskData = 0,
|
||||
|
||||
[Parameter(Mandatory = $true)] # this will decide Azure subscription
|
||||
[string]
|
||||
$Subnet,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$OS,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$Environment,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$Datacenter,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]
|
||||
$LicensingRestrictions,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
switch ($Platform) {
|
||||
'VMware' {
|
||||
# New-ITDWindowsVmVmware
|
||||
}
|
||||
'Azure' {
|
||||
# New-ITDWindowsVmAzure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+694
@@ -0,0 +1,694 @@
|
||||
<# ################
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
$NewVMWindowsAzureParams = @{
|
||||
ComputerName = 'itdzmtest701.nd.gov';
|
||||
ResourceGroupNameOverride = 'rg-shared-iis-tst';
|
||||
AvailabilityZone = 2;
|
||||
CPU = 2;
|
||||
MemoryGB = 16;
|
||||
DiskOsGB = 128;
|
||||
DiskDataGB = 0;
|
||||
Subnet = '10.21.8.0/22';
|
||||
OS = 'Windows Server 2022 Datacenter';
|
||||
Environment = 'Test';
|
||||
AppName = 'ITD-POC-zmeier';
|
||||
LicensingRestrictions = "No Licensing Restrictions";
|
||||
}
|
||||
|
||||
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
|
||||
#>
|
||||
|
||||
function New-ITDWindowsVmAzure {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[string]
|
||||
$ResourceGroupNameOverride,
|
||||
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[ValidateSet(1, 2, 3)]
|
||||
[int]
|
||||
$AvailabilityZone,
|
||||
|
||||
[int]
|
||||
$CPU,
|
||||
|
||||
[int]
|
||||
$MemoryGB,
|
||||
|
||||
[int]
|
||||
$DiskOsGB,
|
||||
|
||||
[int]
|
||||
$DiskDataGB,
|
||||
|
||||
[string]
|
||||
$Subnet,
|
||||
|
||||
[string]
|
||||
$OS,
|
||||
|
||||
[string]
|
||||
$Environment,
|
||||
|
||||
#[string]
|
||||
#$Subscription,
|
||||
|
||||
[string]
|
||||
$LicensingRestrictions,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
$ComputerName = $ComputerName.ToLower()
|
||||
$FQDN = $ComputerName
|
||||
$Hostname = $FQDN.split('.')[0]
|
||||
|
||||
Write-Verbose -Message "Prepare Connections"
|
||||
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
|
||||
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
|
||||
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
|
||||
#$SecureStringPwd = 'Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx'
|
||||
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
|
||||
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
|
||||
|
||||
Write-Verbose -Message "Prepare Credentials"
|
||||
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
|
||||
|
||||
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
|
||||
Clear-DnsClientCache
|
||||
$Cidr = $Subnet
|
||||
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
|
||||
#######
|
||||
####### Remove 10.10.10.10 references when DNS sync is fixed
|
||||
#######
|
||||
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress
|
||||
$SubnetMaskInt = $CIDR.split('/')[1]
|
||||
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
|
||||
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
|
||||
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
|
||||
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
|
||||
([math]::Truncate($Int64 % 256)).ToString()
|
||||
$IPSplit = $Subnet.Split('.')
|
||||
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
|
||||
|
||||
If ($null -ne $IpAddress) {
|
||||
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
|
||||
Write-Warning "DNS record already exists, CIDR Block match"
|
||||
}
|
||||
Else {
|
||||
Write-Error "DNS record already exists, and does not match CIDR Block"
|
||||
Break
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
|
||||
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Verbose -Message ("FQDN is " + $FQDN)
|
||||
#######
|
||||
####### Remove 10.10.10.10 references when DNS sync is fixed
|
||||
#######
|
||||
#[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
|
||||
|
||||
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
|
||||
|
||||
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
|
||||
Write-Error "IP Address already in use." -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
|
||||
If ($FQDN -like "itdcnd*") {
|
||||
$PasswordStateList = "Peoplesoft Share PW"
|
||||
}
|
||||
Else {
|
||||
$PasswordStateList = "CSRC"
|
||||
}
|
||||
|
||||
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
|
||||
If ($GuestVMLocalCredential) {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
|
||||
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
|
||||
}
|
||||
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
|
||||
Write-Verbose -Message "Determine environment"
|
||||
switch ($Environment) {
|
||||
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
|
||||
Write-Verbose -Message "Environment is Test or Development"
|
||||
$EnvShortString = 'tst'
|
||||
}
|
||||
'Production' {
|
||||
Write-Verbose -Message "Environment is Production"
|
||||
$EnvShortString = 'prd'
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Determine subscription, via Subnet"
|
||||
$AllSubscriptions = Get-AzSubscription
|
||||
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
|
||||
Set-AzContext -SubscriptionObject $Subscription | Out-Null
|
||||
Get-AzVirtualNetwork | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
Subscription = $Subscription.Name;
|
||||
VirtualNetwork = $_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
|
||||
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
|
||||
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
|
||||
|
||||
$VNetName = $VirtualNetwork.Name
|
||||
$VNetSubnet = $VirtualNetworkSubnet.Name
|
||||
|
||||
switch ($OS) {
|
||||
"Windows Server 2019 Datacenter" {
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsServer"
|
||||
$Offer = "WindowsServer"
|
||||
$sku = "2019-Datacenter"
|
||||
}
|
||||
"Windows Server 2022 Datacenter" {
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsServer"
|
||||
$Offer = "WindowsServer"
|
||||
$sku = "2022-datacenter"
|
||||
}
|
||||
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
|
||||
}
|
||||
|
||||
# finalize VM location and size
|
||||
$location = "centralus"
|
||||
$VMSizeFilter1 = @(
|
||||
"Standard_D2ds_v5",
|
||||
"Standard_D4ds_v5",
|
||||
"Standard_D8ds_v5",
|
||||
"Standard_D16ds_v5",
|
||||
"Standard_D32ds_v5",
|
||||
"Standard_E2ds_v5",
|
||||
"Standard_E4ds_v5",
|
||||
"Standard_E8ds_v5",
|
||||
"Standard_E16ds_v5",
|
||||
"Standard_E20ds_v5",
|
||||
"Standard_E32ds_v5",
|
||||
"Standard_F2s_v2",
|
||||
"Standard_F4s_v2",
|
||||
"Standard_F8s_v2",
|
||||
"Standard_F16s_v2",
|
||||
"Standard_F32s_v2"
|
||||
)
|
||||
|
||||
$VMSize = Get-AzVMSize -Location centralus | `
|
||||
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
|
||||
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
|
||||
Where-Object Name -NotMatch "_Promo" | `
|
||||
Sort-Object NumberOfCores, MemoryInMB | `
|
||||
Select-Object -First 1
|
||||
|
||||
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
|
||||
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
|
||||
# use the name in the variable
|
||||
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
|
||||
$ResourceGroupName = $ResourceGroupNameOverride
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
|
||||
# if name is not given, determine the name
|
||||
$VMOwner = $AppName.split('-')[0].ToLower()
|
||||
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
|
||||
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
|
||||
}
|
||||
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
|
||||
|
||||
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
|
||||
Set-AzContext $Subscription | Out-Null
|
||||
$ResGroupExist = $null
|
||||
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
|
||||
|
||||
If (@($ResGroupExist).count -gt 1) {
|
||||
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
|
||||
}
|
||||
If (@($ResGroupExist).count -lt 1) {
|
||||
Write-Warning "No matching resource group found, creating it now"
|
||||
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
|
||||
}
|
||||
If (@($ResGroupExist).count -eq 1) {
|
||||
Write-Verbose -Message "Exactly one matching resource group found"
|
||||
}
|
||||
|
||||
$VMName = "vm-$HostName-$EnvShortString"
|
||||
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
|
||||
$VMNicName = "nic-$HostName-$EnvShortString"
|
||||
$VMObjectName = "vm-$HostName-$EnvShortString"
|
||||
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
|
||||
|
||||
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
|
||||
$VMIPConfigName = $VMIPConfigName.ToLower()
|
||||
$VMNicName = $VMNicName.ToLower()
|
||||
$VMObjectName = $VMObjectName.ToLower()
|
||||
$VMOsDiskName = $VMOSDiskName.ToLower()
|
||||
|
||||
Write-Verbose -Message "Creating IPConfig and NIC"
|
||||
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
|
||||
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
|
||||
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
|
||||
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
|
||||
Set-AzNetworkInterface -NetworkInterface $VMNIC
|
||||
|
||||
Write-Verbose -Message "Build VM Config"
|
||||
$vmConfigParams = @{
|
||||
VMName = $VMObjectName
|
||||
VMSize = $VMSize.Name
|
||||
}
|
||||
|
||||
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
|
||||
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
|
||||
$vmConfigParams += @{
|
||||
Zone = $AvailabilityZone
|
||||
}
|
||||
}
|
||||
|
||||
$vmConfig = New-AzVMConfig @vmConfigParams
|
||||
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
|
||||
If ($VMOS -eq "Windows") {
|
||||
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
|
||||
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
|
||||
$vmConfig.OSProfile.ComputerName = $HostName
|
||||
}
|
||||
If ($VMOS -eq "Linux") {
|
||||
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
|
||||
Switch ($VMSubscription) {
|
||||
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
|
||||
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
|
||||
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
|
||||
}
|
||||
}
|
||||
|
||||
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
|
||||
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
|
||||
|
||||
Write-Verbose "Creating VM"
|
||||
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
|
||||
|
||||
Start-Sleep -Seconds 60
|
||||
|
||||
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
|
||||
|
||||
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
|
||||
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$environment-") } })
|
||||
|
||||
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
|
||||
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
|
||||
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
|
||||
|
||||
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
|
||||
|
||||
$count = 0
|
||||
|
||||
If ($ExistingDisks) {
|
||||
while ($Size -match $ExistingDisks.DiskSizeGB) {
|
||||
$count++
|
||||
Write-Verbose -Message "SizeGB: $Size, Count: $count"
|
||||
If ($count -ge 11) {
|
||||
Write-Error "Disk size not available" -ErrorAction Stop
|
||||
}
|
||||
Else {
|
||||
$Size = $Size - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "SizeGB: $Size, Count: $count"
|
||||
|
||||
$AzureRmDiskConfigParams = @{
|
||||
DiskSizeGB = $DiskDataGB
|
||||
Location = $Location
|
||||
CreateOption = "Empty"
|
||||
SkuName = "Premium_LRS"
|
||||
}
|
||||
|
||||
If ($Zone) {
|
||||
Write-Verbose "VM is located in Zone $Zone"
|
||||
$AzureRmDiskConfigParams += @{Zone = $Zone }
|
||||
}
|
||||
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
|
||||
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
|
||||
|
||||
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
|
||||
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
|
||||
|
||||
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
|
||||
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization"
|
||||
Start-Sleep -Seconds 120
|
||||
|
||||
Write-Verbose -Message "Begin Pre-Firewall Guest OS customization"
|
||||
$InvokeAzVMRunCommandParams = @{
|
||||
ResourceGroupName = $ResourceGroupName;
|
||||
Name = $VMName;
|
||||
CommandId = 'RunPowerShellScript'
|
||||
}
|
||||
|
||||
Write-Verbose -Message "1-Set WMI Tags"
|
||||
$ScriptBlock = {
|
||||
try {
|
||||
Write-Verbose "Create new Class"
|
||||
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
|
||||
|
||||
$Class["__CLASS"] = "ITD";
|
||||
$Class.Qualifiers.Add("Static", $true)
|
||||
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
|
||||
|
||||
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
|
||||
|
||||
$Class.Put()
|
||||
|
||||
Write-Verbose "Create single ITD Object"
|
||||
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "3-Disk Configuration"
|
||||
$ScriptBlock = {
|
||||
try {
|
||||
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
|
||||
$disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
|
||||
|
||||
if ($disks) {
|
||||
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
|
||||
|
||||
# Prevent the "You must format this partition before using it." popup
|
||||
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue }
|
||||
|
||||
foreach ($disk in $disks) {
|
||||
if ($disk.IsOffline) {
|
||||
Set-Disk $disk.Number -IsOffline $false
|
||||
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
|
||||
}
|
||||
|
||||
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
|
||||
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
|
||||
|
||||
$diskParam = @{
|
||||
FileSystem = 'NTFS'
|
||||
Confirm = $false
|
||||
}
|
||||
|
||||
$driveLetter = [Int][Char]'D'
|
||||
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
|
||||
$driveLetter++
|
||||
}
|
||||
|
||||
$diskParam.DriveLetter = [Char]$driveLetter
|
||||
|
||||
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Temporary Storage'
|
||||
}
|
||||
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Data'
|
||||
}
|
||||
|
||||
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
|
||||
[void](Format-Volume @diskParam)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "No unpartitioned disks found, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
finally {
|
||||
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "5-Time Zone"
|
||||
$ScriptBlock = {
|
||||
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
|
||||
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time."
|
||||
|
||||
Set-TimeZone -Id 'Central Standard Time'
|
||||
|
||||
Write-Verbose -Message "Time zone set to Central Standard Time."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Time zone is already set to Central Standard Time."
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "6-Enable Performance Counters"
|
||||
$ScriptBlock = {
|
||||
# Enable Performance Counters
|
||||
try {
|
||||
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
|
||||
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
|
||||
|
||||
Write-Verbose -Message "Performance monitors enabled."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Performance monitors already enabled, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "7-Disable Windows Firewall"
|
||||
$ScriptBlock = {
|
||||
# Disable Windows Firewall
|
||||
Write-Verbose -Message "Checking for active Windows Firewall..."
|
||||
|
||||
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
|
||||
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
|
||||
|
||||
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
|
||||
|
||||
Write-Verbose -Message "Windows Firewall disabled."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Windows Firewall already disabled, continuing..."
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
# determine Active Directory Forest and OU
|
||||
Write-Verbose -Message "8a-Determine domain join"
|
||||
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
|
||||
switch ($DomainName) {
|
||||
'nd.gov' {
|
||||
$SearchBaseDomain = "dc=nd,dc=gov"
|
||||
}
|
||||
'ndcloud.gov' {
|
||||
$SearchBaseDomain = "dc=ndcloud,dc=gov"
|
||||
}
|
||||
}
|
||||
|
||||
If ($DomainName -eq "nd.gov") {
|
||||
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
|
||||
If (!($OUAppName)) {
|
||||
$OUAppName = Get-ADOrganizationalUnit -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
|
||||
}
|
||||
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $Hostname }
|
||||
If ($ExistingADComputer) {
|
||||
If ($ExistingADComputer.DistinguishedName -like ("*" + $AppName + "*") -or $ExistingADComputer.DistinguishedName -like "*All-General*") {
|
||||
Write-Warning "AD object already exists, OU path does match"
|
||||
$OuFinal = $ExistingADComputer.DistinguishedName -replace '^.+?(?<!\\),', ''
|
||||
}
|
||||
Else {
|
||||
Write-Error "AD object already exists, OU path mismatch"
|
||||
Exit
|
||||
Exit
|
||||
}
|
||||
}
|
||||
Else {
|
||||
switch ($Environment) {
|
||||
'Test' {
|
||||
If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Non-Prod" }
|
||||
Else { $EnvString = "Test" }
|
||||
$OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
|
||||
}
|
||||
'Production' {
|
||||
If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Prod" }
|
||||
Else { $EnvString = "Prod" }
|
||||
|
||||
$OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
|
||||
}
|
||||
}
|
||||
If ($OuAppNameEnv) { $OUFinal = $OUAppNameEnv.DistinguishedName }
|
||||
Else { $OuFinal = $OUAppName.DistinguishedName }
|
||||
}
|
||||
}
|
||||
|
||||
# wait for firewall and reconnect
|
||||
Write-Verbose -Message "8b-Verify if firewall is open before domain join attempt"
|
||||
$connectivity = $false
|
||||
while ($connectivity -eq $false) {
|
||||
If (Test-NetConnection -ComputerName $IpAddress) {
|
||||
Write-Verbose -Message "Ping successful" -Verbose
|
||||
$connectivity = $true
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Ping failed, may need to wait for PA IP sync" -Verbose
|
||||
Start-Sleep -Seconds 60
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "8c-Attempt domain join"
|
||||
switch ($DomainName) {
|
||||
'nd.gov' {
|
||||
Write-Verbose -Message "Attempting domain join nd.gov"
|
||||
Write-Verbose -Message "Domain: $DomainName"
|
||||
Write-Verbose -Message "OuPath: $OuFinal"
|
||||
<#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptPath 'C:\AzWinBuild\8-Domain-ndgov.ps1' -Parameter @{
|
||||
"DomainName" = 'nd.gov';
|
||||
"OuPath" = "$OuFinal"
|
||||
}#>
|
||||
$ScriptBlock = {
|
||||
Param(
|
||||
[string]
|
||||
$DomainName,
|
||||
|
||||
[string]
|
||||
$OuPath
|
||||
)
|
||||
|
||||
Write-Host $OuPath
|
||||
Test-NetConnection -ComputerName nd.gov
|
||||
|
||||
$DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force))
|
||||
Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred
|
||||
|
||||
Restart-Computer -Force
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{
|
||||
"DomainName" = 'nd.gov';
|
||||
"OuPath" = "$OuFinal"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 120
|
||||
|
||||
Write-Verbose -Message "10a-Copy SCCM client from itdsccmp2.nd.gov" -Verbose
|
||||
$ScriptBlock = { #### NEEDS WORK
|
||||
Param(
|
||||
|
||||
)
|
||||
|
||||
Copy-Item -path "\\itdsccmp2.nd.gov\SCCM_Client\Client\" -Destination C:\temp\SCCM_Client -Recurse
|
||||
}
|
||||
|
||||
$CopySuccess = $false
|
||||
$CopyAttempts = 0
|
||||
While ($CopySuccess -eq $false -and $CopyAttempts -lt 100) {
|
||||
$Attempts++
|
||||
$InvokeResult = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
If ($InvokeResult.value.message -like "Copy-Item : Cannot find path*") {
|
||||
Write-Verbose -Message "SCCM Client file copy failed. Looping until it works." -Verbose
|
||||
$CopySuccess = $false
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "SCCM Client file copy success." -Verbose
|
||||
$CopySuccess = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Check if SCCM automatically installed the SCCM client and registered it
|
||||
$CcmRegistered = $false
|
||||
$CcmRegistration = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString {
|
||||
Get-Content C:\Windows\CCM\Logs\ClientIDManagerStartup.log | Select-String RegTask
|
||||
}
|
||||
If ($CcmRegistration.value.message -match "Client is registered") {
|
||||
Write-Verbose "Client is registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf ($CcmRegistration.value.message -match "Client is already registered") {
|
||||
Write-Verbose "Client is already registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
If ($CcmRegistered -eq $false) {
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString {
|
||||
|
||||
If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) {
|
||||
Write-Warning "CCM client is already installing"
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) {
|
||||
Write-Warning "CCM client is already installed"
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
Else {
|
||||
Write-Warning -Message "Installing SCCM Client..."
|
||||
Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "[$FQDN]:Approve SCCM Client"
|
||||
#Start-Sleep -Seconds 30 # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL
|
||||
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
|
||||
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
|
||||
$PSDrives = Get-PSDrive
|
||||
If ($PSDrives | Where-Object Name -EQ "ITD") {
|
||||
# ITD Drive exists, do nothing
|
||||
}
|
||||
else {
|
||||
New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov
|
||||
}
|
||||
|
||||
Set-Location ITD:\
|
||||
$Device = Get-CMDevice -Name $args[0]
|
||||
If ($Device.IsApproved -eq 0) {
|
||||
Approve-CMDevice -DeviceName $args[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+1037
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>ITD.ITD-WindowsServer.General</id>
|
||||
<version>$VERSIONHERE$</version>
|
||||
<authors>Zack Meier</authors>
|
||||
<description>Functions for Windows Server General administration</description>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\.git\**;**\Build\**" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,132 @@
|
||||
#
|
||||
# Module manifest for module 'ITD.ITD-WindowsServer.General'
|
||||
#
|
||||
# Generated by: zmeier
|
||||
#
|
||||
# Generated on: 6/14/2022
|
||||
#
|
||||
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'ITD.ITD-WindowsServer.General.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '<ModuleVersion>'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = 'Desktop', 'Core'
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '3bb063c4-d6c3-4775-8f25-4bdf31fe4b05'
|
||||
|
||||
# Author of this module
|
||||
Author = 'zmeier'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'State of North Dakota'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) zmeier. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Functions for Windows Server general administration'
|
||||
|
||||
# Minimum version of the PowerShell engine required by this module
|
||||
# PowerShellVersion = ''
|
||||
|
||||
# Name of the PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# ClrVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @(<FunctionsToExport>)
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = $false
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = @()
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#Get public and private function definition files.
|
||||
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
|
||||
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
|
||||
|
||||
#Dot source the files
|
||||
Foreach($import in @($Public + $Private))
|
||||
{
|
||||
Try
|
||||
{
|
||||
. $import.fullname
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Write-Error -Message "Failed to import function $($import.fullname): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Here I might...
|
||||
# Read in or create an initial config file and variable
|
||||
# Export Public functions ($Public.BaseName) for WIP modules
|
||||
# Set variables visible to the module and its functions only
|
||||
|
||||
Export-ModuleMember -Function $Public.Basename
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values.
|
||||
.DESCRIPTION
|
||||
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values. CSR will be printed to the screen, but can be saved to the clipboard, or to a file.
|
||||
Default values are:
|
||||
|
||||
.NOTES
|
||||
Run as administrator is required.
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov'
|
||||
CSR is generated using the common name shown, and default values for everything else
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096
|
||||
CSR is generated using the values specified, defaults for the rest
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096 -ToClipboard
|
||||
CSR is generated using the values specified, defaults for the rest, and saved into the user's clipboard
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -ToPath C:\temp.csr
|
||||
CSR is generated using the common name shown, and default values for everything else, and saves the CSR to a local path
|
||||
#>
|
||||
|
||||
function New-ITDSslCertificateSigningRequest {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$CommonName,
|
||||
|
||||
[string]
|
||||
$Organization = "State of North Dakota",
|
||||
|
||||
[string]
|
||||
$OrganizationalUnit = "NDIT",
|
||||
|
||||
[string]
|
||||
$Locality = "Bismarck",
|
||||
|
||||
[string]
|
||||
$State = "ND",
|
||||
|
||||
[string]
|
||||
$Country = "US",
|
||||
|
||||
[ValidateSet(2048, 4096)]
|
||||
[int]
|
||||
$KeyLength = 4096,
|
||||
|
||||
[switch]
|
||||
$Exportable = $true,
|
||||
|
||||
[ValidateSet('sha256','sha384','sha512','md5')]
|
||||
[string]
|
||||
$HashAlgorithm = "sha256",
|
||||
|
||||
[switch]
|
||||
$ToClipboard,
|
||||
|
||||
[string]
|
||||
$ToPath
|
||||
)
|
||||
|
||||
begin {
|
||||
if (-NOT([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
Write-Host "Administrator priviliges are required. Please restart this script with elevated rights." -ForegroundColor Red
|
||||
Pause
|
||||
Throw "Administrator priviliges are required. Please restart this script with elevated rights."
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
$UID = [guid]::NewGuid()
|
||||
$files = @{}
|
||||
$files['settings'] = "$($env:TEMP)\$($UID)-settings.inf";
|
||||
$files['csr'] = "$($env:TEMP)\$($UID)-csr.req"
|
||||
|
||||
$request = @{}
|
||||
$request['SAN'] = @{}
|
||||
|
||||
#2048, sha256
|
||||
$settingsInf = "
|
||||
[Version]
|
||||
Signature=`"`$Windows NT`$
|
||||
[NewRequest]
|
||||
KeyLength = {{KeyLength}}
|
||||
Exportable = {{Exportable}}
|
||||
MachineKeySet = TRUE
|
||||
SMIME = FALSE
|
||||
RequestType = PKCS10
|
||||
ProviderName = `"Microsoft RSA SChannel Cryptographic Provider`"
|
||||
ProviderType = 12
|
||||
HashAlgorithm = {{HashAlgorithm}}
|
||||
;Variables
|
||||
Subject = `"CN={{CN}},OU={{OU}},O={{O}},L={{L}},S={{S}},C={{C}}`"
|
||||
[Extensions]
|
||||
{{SAN}}
|
||||
;Certreq info
|
||||
;http://technet.microsoft.com/en-us/library/dn296456.aspx
|
||||
;CSR Decoder
|
||||
;https://certlogik.com/decoder/
|
||||
;https://ssltools.websecurity.symantec.com/checker/views/csrCheck.jsp
|
||||
"
|
||||
|
||||
|
||||
$request['SAN_string'] = & {
|
||||
if ($request['SAN'].Count -gt 0) {
|
||||
$san = "2.5.29.17 = `"{text}`"
|
||||
"
|
||||
Foreach ($sanItem In $request['SAN'].Values) {
|
||||
$san += "_continue_ = `"dns=" + $sanItem + "&`"
|
||||
"
|
||||
}
|
||||
return $san
|
||||
}
|
||||
}
|
||||
|
||||
$settingsInf = $settingsInf.Replace("{{CN}}", $CommonName)
|
||||
$settingsInf = $settingsInf.Replace("{{O}}", $Organization)
|
||||
$settingsInf = $settingsInf.Replace("{{OU}}", $OrganizationalUnit)
|
||||
$settingsInf = $settingsInf.Replace("{{L}}", $Locality)
|
||||
$settingsInf = $settingsInf.Replace("{{S}}", $State)
|
||||
$settingsInf = $settingsInf.Replace("{{C}}", $Country)
|
||||
$settingsInf = $settingsInf.Replace("{{SAN}}", $request['SAN_string'])
|
||||
$settingsInf = $settingsInf.Replace("{{KeyLength}}",$KeyLength)
|
||||
$settingsInf = $settingsInf.Replace("{{HashAlgorithm}}",$HashAlgorithm)
|
||||
$settingsInf = $settingsInf.Replace("{{Exportable}}",$Exportable)
|
||||
|
||||
# Save settings to file in temp
|
||||
$settingsInf > $files['settings']
|
||||
|
||||
certreq -new $files['settings'] $files['csr'] > $null
|
||||
|
||||
$CSR = Get-Content $files['csr']
|
||||
|
||||
Write-Output $CSR
|
||||
If ($ToClipboard) {
|
||||
$CSR | Set-Clipboard
|
||||
}
|
||||
If ($ToPath) {
|
||||
$CSR | Out-File -FilePath $ToPath
|
||||
}
|
||||
|
||||
$files.Values | ForEach-Object {
|
||||
Remove-Item $_ -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values.
|
||||
.DESCRIPTION
|
||||
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values. CSR will be printed to the screen, but can be saved to the clipboard, or to a file.
|
||||
Default values are:
|
||||
|
||||
.NOTES
|
||||
Run as administrator is required.
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov'
|
||||
CSR is generated using the common name shown, and default values for everything else
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096
|
||||
CSR is generated using the values specified, defaults for the rest
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096 -ToClipboard
|
||||
CSR is generated using the values specified, defaults for the rest, and saved into the user's clipboard
|
||||
.EXAMPLE
|
||||
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -ToPath C:\temp.csr
|
||||
CSR is generated using the common name shown, and default values for everything else, and saves the CSR to a local path
|
||||
#>
|
||||
|
||||
function New-ITDSslCertificateSigningRequest {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$CommonName,
|
||||
|
||||
[string]
|
||||
$Organization = "State of North Dakota",
|
||||
|
||||
[string]
|
||||
$OrganizationalUnit = "NDIT",
|
||||
|
||||
[string]
|
||||
$Locality = "Bismarck",
|
||||
|
||||
[string]
|
||||
$State = "ND",
|
||||
|
||||
[string]
|
||||
$Country = "US",
|
||||
|
||||
[ValidateSet(2048, 4096)]
|
||||
[int]
|
||||
$KeyLength = 4096,
|
||||
|
||||
[switch]
|
||||
$Exportable = $true,
|
||||
|
||||
[ValidateSet('sha256','sha384','sha512','md5')]
|
||||
[string]
|
||||
$HashAlgorithm = "sha256",
|
||||
|
||||
[switch]
|
||||
$ToClipboard,
|
||||
|
||||
[string]
|
||||
$ToPath
|
||||
)
|
||||
|
||||
begin {
|
||||
if (-NOT([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
Write-Host "Administrator priviliges are required. Please restart this script with elevated rights." -ForegroundColor Red
|
||||
Pause
|
||||
Throw "Administrator priviliges are required. Please restart this script with elevated rights."
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
$UID = [guid]::NewGuid()
|
||||
$files = @{}
|
||||
$files['settings'] = "$($env:TEMP)\$($UID)-settings.inf";
|
||||
$files['csr'] = "$($env:TEMP)\$($UID)-csr.req"
|
||||
|
||||
$request = @{}
|
||||
$request['SAN'] = @{}
|
||||
|
||||
#2048, sha256
|
||||
$settingsInf = "
|
||||
[Version]
|
||||
Signature=`"`$Windows NT`$
|
||||
[NewRequest]
|
||||
KeyLength = {{KeyLength}}
|
||||
Exportable = {{Exportable}}
|
||||
MachineKeySet = TRUE
|
||||
SMIME = FALSE
|
||||
RequestType = PKCS10
|
||||
ProviderName = `"Microsoft RSA SChannel Cryptographic Provider`"
|
||||
ProviderType = 12
|
||||
HashAlgorithm = {{HashAlgorithm}}
|
||||
;Variables
|
||||
Subject = `"CN={{CN}},OU={{OU}},O={{O}},L={{L}},S={{S}},C={{C}}`"
|
||||
[Extensions]
|
||||
{{SAN}}
|
||||
;Certreq info
|
||||
;http://technet.microsoft.com/en-us/library/dn296456.aspx
|
||||
;CSR Decoder
|
||||
;https://certlogik.com/decoder/
|
||||
;https://ssltools.websecurity.symantec.com/checker/views/csrCheck.jsp
|
||||
"
|
||||
|
||||
|
||||
$request['SAN_string'] = & {
|
||||
if ($request['SAN'].Count -gt 0) {
|
||||
$san = "2.5.29.17 = `"{text}`"
|
||||
"
|
||||
Foreach ($sanItem In $request['SAN'].Values) {
|
||||
$san += "_continue_ = `"dns=" + $sanItem + "&`"
|
||||
"
|
||||
}
|
||||
return $san
|
||||
}
|
||||
}
|
||||
|
||||
$settingsInf = $settingsInf.Replace("{{CN}}", $CommonName)
|
||||
$settingsInf = $settingsInf.Replace("{{O}}", $Organization)
|
||||
$settingsInf = $settingsInf.Replace("{{OU}}", $OrganizationalUnit)
|
||||
$settingsInf = $settingsInf.Replace("{{L}}", $Locality)
|
||||
$settingsInf = $settingsInf.Replace("{{S}}", $State)
|
||||
$settingsInf = $settingsInf.Replace("{{C}}", $Country)
|
||||
$settingsInf = $settingsInf.Replace("{{SAN}}", $request['SAN_string'])
|
||||
$settingsInf = $settingsInf.Replace("{{KeyLength}}",$KeyLength)
|
||||
$settingsInf = $settingsInf.Replace("{{HashAlgorithm}}",$HashAlgorithm)
|
||||
$settingsInf = $settingsInf.Replace("{{Exportable}}",$Exportable)
|
||||
|
||||
# Save settings to file in temp
|
||||
$settingsInf > $files['settings']
|
||||
|
||||
certreq -new $files['settings'] $files['csr'] > $null
|
||||
|
||||
$CSR = Get-Content $files['csr']
|
||||
|
||||
Write-Output $CSR
|
||||
If ($ToClipboard) {
|
||||
$CSR | Set-Clipboard
|
||||
}
|
||||
If ($ToPath) {
|
||||
$CSR | Out-File -FilePath $ToPath
|
||||
}
|
||||
|
||||
$files.Values | ForEach-Object {
|
||||
Remove-Item $_ -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
New-ITDAutomationRecord -AppName "Windows-General" -Action "Provisioning" -Minutes 3 -Platform "PowerShell-ITD.Windows"
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
function Remove-ITDSolarwindsNode {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
}
|
||||
|
||||
process {
|
||||
|
||||
$Func = {
|
||||
Import-Module -Name ITDSolarwinds -Verbose
|
||||
$test = Get-SWNode -ComputerName $args[0]
|
||||
If ($test) {
|
||||
Write-Warning "Solarwinds node found, removing it..."
|
||||
Remove-SWNode -ComputerName $args[0]
|
||||
}
|
||||
Else {
|
||||
Write-Warning "Solarwinds node not found"
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $ComputerName -Credential $Credential
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
@@ -0,0 +1,19 @@
|
||||
# figure out where to put it
|
||||
$x = Get-ADComputer -Filter * -Server itdk12dc7.k12.nd.us -Credential $K12Cred -Properties CanonicalName
|
||||
|
||||
|
||||
|
||||
# enter this on the machine itself
|
||||
$Credential = Get-Credential
|
||||
$AddComputerParams = @{
|
||||
DomainName = 'k12.nd.us';
|
||||
OUPath = 'OU=Prod,OU=All-General,OU=Computers,OU=ITD,DC=k12,DC=nd,DC=us' ;
|
||||
Credential = $Credential;
|
||||
}
|
||||
Add-Computer @AddComputerParams
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
name: 'ITD.ITD-WindowsServer.Lifecycle'
|
||||
|
||||
variables:
|
||||
major: 0
|
||||
minor: 1
|
||||
patch: $(Build.BuildID)
|
||||
buildVer: $(major).$(minor).$(Build.BuildID)
|
||||
|
||||
pool: itdwinautop1
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: Build
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.Lifecycle.nuspec'
|
||||
versioningScheme: byEnvVar
|
||||
versionEnvVar: buildVer
|
||||
buildProperties: 'VERSIONHERE=$(buildVer)'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'NuGetPackage'
|
||||
publishLocation: 'Container'
|
||||
- stage: Deploy
|
||||
jobs:
|
||||
- job: Deploy
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'NuGetPackage'
|
||||
itemPattern: '**'
|
||||
targetPath: '$(Pipeline.Workspace)'
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'push'
|
||||
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.Lifecycle.$(major).$(minor).$(Build.BuildID).nupkg'
|
||||
nuGetFeedType: external
|
||||
publishFeedCredentials: 'ITD_PwshGallery'
|
||||
@@ -0,0 +1,17 @@
|
||||
$buildVersion = $env:BUILDVER
|
||||
$moduleName = 'ITD.ITD-WindowsServer.Lifecycle'
|
||||
|
||||
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
|
||||
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
|
||||
|
||||
## Update build version in manifest
|
||||
$manifestContent = Get-Content -Path $manifestPath -Raw
|
||||
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
|
||||
|
||||
## Update functions to export in manifest
|
||||
Import-Module $modulePath
|
||||
$funcStrings = (Get-Module ITD.ITD-WindowsServer.Lifecycle).ExportedCommands.Values.Name
|
||||
$funcStrings = "'$($funcStrings -join "','")'"
|
||||
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
|
||||
|
||||
$manifestContent | Set-Content -Path $manifestPath
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>ITD.ITD-WindowsServer.Lifecycle</id>
|
||||
<version>$VERSIONHERE$</version>
|
||||
<authors>Zack Meier</authors>
|
||||
<description>Functions for Windows Server Lifecycle administration</description>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="**\.git\**;**\Build\**" />
|
||||
</files>
|
||||
</package>
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
#
|
||||
# Module manifest for module 'ITD.ITD-WindowsServer.Lifecycle'
|
||||
#
|
||||
# Generated by: zmeier
|
||||
#
|
||||
# Generated on: 6/14/2022
|
||||
#
|
||||
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'ITD.ITD-WindowsServer.Lifecycle.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '<ModuleVersion>'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = 'Desktop', 'Core'
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '42b3d283-1030-4bf9-afa7-4810061f74a8'
|
||||
|
||||
# Author of this module
|
||||
Author = 'zmeier'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'State of North Dakota'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) zmeier. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Functions for Windows Server general administration'
|
||||
|
||||
# Minimum version of the PowerShell engine required by this module
|
||||
# PowerShellVersion = ''
|
||||
|
||||
# Name of the PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# ClrVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @(<FunctionsToExport>)
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = $false
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = @()
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#Get public and private function definition files.
|
||||
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
|
||||
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
|
||||
|
||||
#Dot source the files
|
||||
Foreach($import in @($Public + $Private))
|
||||
{
|
||||
Try
|
||||
{
|
||||
. $import.fullname
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Write-Error -Message "Failed to import function $($import.fullname): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Here I might...
|
||||
# Read in or create an initial config file and variable
|
||||
# Export Public functions ($Public.BaseName) for WIP modules
|
||||
# Set variables visible to the module and its functions only
|
||||
|
||||
Export-ModuleMember -Function $Public.Basename
|
||||
+447
@@ -0,0 +1,447 @@
|
||||
<# ################
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
$NewVMWindowsAzureParams = @{
|
||||
ComputerName = 'itdzmtest901.nd.gov';
|
||||
CPU = 2;
|
||||
MemoryGB = 8;
|
||||
DiskOsGB = 128;
|
||||
DiskDataGB = 0;
|
||||
Subnet = '10.21.8.0/22';
|
||||
OS = "Windows Server 2025 Datacenter";
|
||||
Environment = 'Test';
|
||||
AppName = 'ITD-POC-zmeier';
|
||||
LicensingRestrictions = "No Licensing Restrictions";
|
||||
}
|
||||
|
||||
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
|
||||
.EXAMPLE
|
||||
$NewVMWindowsAzureParams = @{
|
||||
ComputerName = 'itdzmtest701.nd.gov';
|
||||
ResourceGroupNameOverride = 'rg-shared-iis-tst';
|
||||
AvailabilityZone = 2;
|
||||
CPU = 2;
|
||||
MemoryGB = 8;
|
||||
DiskOsGB = 128;
|
||||
DiskDataGB = 0;
|
||||
Subnet = '10.21.8.0/22';
|
||||
OS = 'Windows Server 2022 Datacenter';
|
||||
Environment = 'Test';
|
||||
AppName = 'ITD-POC-zmeier';
|
||||
LicensingRestrictions = "No Licensing Restrictions";
|
||||
}
|
||||
|
||||
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
|
||||
#>
|
||||
|
||||
function New-ITDWindowsVmAzureStep1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$FQDN,
|
||||
|
||||
[string]
|
||||
$ResourceGroupNameOverride,
|
||||
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[ValidateSet(1, 2, 3)]
|
||||
[int]
|
||||
$AvailabilityZone,
|
||||
|
||||
[Parameter(ParameterSetName = 'VmSizeOverride')]
|
||||
[string]
|
||||
$VMSizeOverride,
|
||||
|
||||
[Parameter(ParameterSetName = 'VmSizeDetermine')]
|
||||
[int]
|
||||
$CPU,
|
||||
|
||||
[Parameter(ParameterSetName = 'VmSizeDetermine')]
|
||||
[int]
|
||||
$MemoryGB,
|
||||
|
||||
[int]
|
||||
$DiskOsGB,
|
||||
|
||||
[int]
|
||||
$DiskDataGB,
|
||||
|
||||
[string]
|
||||
$Subnet,
|
||||
|
||||
[string]
|
||||
$OS,
|
||||
|
||||
[string]
|
||||
$VMEnvironment,
|
||||
|
||||
#[string]
|
||||
#$Subscription,
|
||||
|
||||
[string]
|
||||
$LicensingRestrictions,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
$FQDN = $FQDN.ToLower()
|
||||
$Hostname = $FQDN.split('.')[0]
|
||||
|
||||
Write-Verbose -Message "Prepare Connections"
|
||||
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
|
||||
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
|
||||
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
|
||||
#$SecureStringPwd = 'sQV8Q~sNLcrDl2RgB32gsSEsDVgdFhNMcBdPoaEX'
|
||||
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
|
||||
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
|
||||
|
||||
Write-Verbose -Message "Prepare Credentials"
|
||||
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
|
||||
|
||||
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
|
||||
Clear-DnsClientCache
|
||||
$Cidr = $Subnet
|
||||
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
|
||||
#######
|
||||
####### Remove 10.10.10.10 references when DNS sync is fixed
|
||||
#######
|
||||
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress
|
||||
$SubnetMaskInt = $CIDR.split('/')[1]
|
||||
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
|
||||
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
|
||||
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
|
||||
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
|
||||
([math]::Truncate($Int64 % 256)).ToString()
|
||||
$IPSplit = $Subnet.Split('.')
|
||||
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
|
||||
|
||||
If ($null -ne $IpAddress) {
|
||||
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
|
||||
Write-Warning "DNS record already exists, CIDR Block match"
|
||||
}
|
||||
Else {
|
||||
Write-Error "DNS record already exists, and does not match CIDR Block"
|
||||
Break
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
|
||||
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Verbose -Message ("FQDN is " + $FQDN)
|
||||
#######
|
||||
####### Remove 10.10.10.10 references when DNS sync is fixed
|
||||
#######
|
||||
#[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
|
||||
|
||||
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
|
||||
|
||||
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
|
||||
Write-Error "IP Address already in use." -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
|
||||
If ($FQDN -like "itdcnd*") {
|
||||
$PasswordStateList = "Peoplesoft Share PW"
|
||||
}
|
||||
Else {
|
||||
$PasswordStateList = "CSRC"
|
||||
}
|
||||
|
||||
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential
|
||||
If ($null -eq $GuestVMLocalCredential) {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
|
||||
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
|
||||
}
|
||||
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
|
||||
Write-Verbose -Message "Determine environment"
|
||||
switch ($VMEnvironment) {
|
||||
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
|
||||
Write-Verbose -Message "Environment is Test or Development"
|
||||
$EnvShortString = 'tst'
|
||||
}
|
||||
'Production' {
|
||||
Write-Verbose -Message "Environment is Production"
|
||||
$EnvShortString = 'prd'
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Determine subscription, via Subnet"
|
||||
$AllSubscriptions = Get-AzSubscription
|
||||
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
|
||||
Set-AzContext -SubscriptionObject $Subscription | Out-Null
|
||||
Get-AzVirtualNetwork | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
Subscription = $Subscription.Name;
|
||||
VirtualNetwork = $_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
|
||||
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
|
||||
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
|
||||
|
||||
$VNetName = $VirtualNetwork.Name
|
||||
$VNetSubnet = $VirtualNetworkSubnet.Name
|
||||
|
||||
Write-Verbose -Message "OS is $OS"
|
||||
switch ($OS) {
|
||||
{ $_ -eq "Windows Server 2019 Datacenter" -or $_ -eq "Windows Server 2019" } {
|
||||
Write-Verbose -Message "OS is Windows Server 2019"
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsServer"
|
||||
$Offer = "WindowsServer"
|
||||
$sku = "2019-Datacenter"
|
||||
$SecurityType = ''
|
||||
}
|
||||
{$_ -eq "Windows Server 2022 Datacenter" -or $_ -eq "Windows Server 2022" } {
|
||||
Write-Verbose -Message "OS is Windows Server 2022"
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsServer"
|
||||
$Offer = "windowsserver2022"
|
||||
$sku = "2022-datacenter"
|
||||
$SecurityType = ''
|
||||
}
|
||||
{ $_ -eq "Windows Server 2025 Datacenter" -or $_ -eq "Windows Server 2025" } {
|
||||
Write-Verbose -Message "OS is Windows Server 2025"
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsServer"
|
||||
$Offer = "WindowsServer"
|
||||
$sku = "2025-datacenter"
|
||||
$SecurityType = ''
|
||||
}
|
||||
"Windows 11 24H2" {
|
||||
Write-Verbose -Message "OS is Windows 11 24H2"
|
||||
$VMOS = "Windows"
|
||||
$Publisher = "MicrosoftWindowsDesktop"
|
||||
$Offer = "Windows-11"
|
||||
$sku = "win11-24h2-ent"
|
||||
$SecurityType = ''
|
||||
}
|
||||
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
|
||||
}
|
||||
|
||||
# finalize VM location and size
|
||||
$location = "centralus"
|
||||
|
||||
switch ($PSCmdLet.ParameterSetName) {
|
||||
"VmSizeOverride" {
|
||||
Write-Verbose -Message "ParameterSet is VmSizeOverride"
|
||||
$VMSize = Get-AzVMSize -Location centralus | Where-Object { $_.Name -eq $VMSizeOverride }
|
||||
}
|
||||
"VmSizeDetermine" {
|
||||
Write-Verbose -Message "ParameterSet is VmSizeDetermine"
|
||||
$VMSizeFilter1 = @(
|
||||
"Standard_D2ds_v5",
|
||||
"Standard_D4ds_v5",
|
||||
"Standard_D8ds_v5",
|
||||
"Standard_D16ds_v5",
|
||||
"Standard_D32ds_v5",
|
||||
"Standard_E2ds_v5",
|
||||
"Standard_E4ds_v5",
|
||||
"Standard_E8ds_v5",
|
||||
"Standard_E16ds_v5",
|
||||
"Standard_E20ds_v5",
|
||||
"Standard_E32ds_v5",
|
||||
"Standard_F2s_v2",
|
||||
"Standard_F4s_v2",
|
||||
"Standard_F8s_v2",
|
||||
"Standard_F16s_v2",
|
||||
"Standard_F32s_v2"
|
||||
)
|
||||
|
||||
$VMSize = Get-AzVMSize -Location centralus | `
|
||||
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
|
||||
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
|
||||
Where-Object Name -NotMatch "_Promo" | `
|
||||
Sort-Object NumberOfCores, MemoryInMB | `
|
||||
Select-Object -First 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
|
||||
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
|
||||
# use the name in the variable
|
||||
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
|
||||
$ResourceGroupName = $ResourceGroupNameOverride
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
|
||||
# if name is not given, determine the name
|
||||
$VMOwner = $AppName.split('-')[0].ToLower()
|
||||
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
|
||||
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
|
||||
}
|
||||
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
|
||||
|
||||
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
|
||||
Set-AzContext $Subscription | Out-Null
|
||||
$ResGroupExist = $null
|
||||
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
|
||||
|
||||
If (@($ResGroupExist).count -gt 1) {
|
||||
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
|
||||
}
|
||||
If (@($ResGroupExist).count -lt 1) {
|
||||
Write-Warning "No matching resource group found, creating it now"
|
||||
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
|
||||
}
|
||||
If (@($ResGroupExist).count -eq 1) {
|
||||
Write-Verbose -Message "Exactly one matching resource group found"
|
||||
}
|
||||
|
||||
$VMName = "vm-$HostName-$EnvShortString"
|
||||
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
|
||||
$VMNicName = "nic-$HostName-$EnvShortString"
|
||||
$VMObjectName = "vm-$HostName-$EnvShortString"
|
||||
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
|
||||
|
||||
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
|
||||
$VMIPConfigName = $VMIPConfigName.ToLower()
|
||||
$VMNicName = $VMNicName.ToLower()
|
||||
$VMObjectName = $VMObjectName.ToLower()
|
||||
$VMOsDiskName = $VMOSDiskName.ToLower()
|
||||
|
||||
Write-Verbose -Message "Creating IPConfig and NIC"
|
||||
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
|
||||
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
|
||||
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
|
||||
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
|
||||
Set-AzNetworkInterface -NetworkInterface $VMNIC
|
||||
|
||||
Write-Verbose -Message "Build VM Config"
|
||||
$vmConfigParams = @{
|
||||
VMName = $VMObjectName
|
||||
VMSize = $VMSize.Name
|
||||
}
|
||||
|
||||
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
|
||||
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
|
||||
$vmConfigParams += @{
|
||||
Zone = $AvailabilityZone
|
||||
}
|
||||
}
|
||||
|
||||
$vmConfig = New-AzVMConfig @vmConfigParams
|
||||
|
||||
# Security Profile
|
||||
switch ($SecurityType) {
|
||||
'TrustedLaunch' {
|
||||
Write-Verbose -Message "Security Profile is TrustedLaunch"
|
||||
$vmConfig = Set-AzVMSecurityProfile -VM $vmConfig -SecurityType "TrustedLaunch"
|
||||
}
|
||||
'' {
|
||||
Write-Verbose -Message "Security Profile is empty"
|
||||
}
|
||||
Default {
|
||||
Write-Verbose -Message "Security Profile is null"
|
||||
}
|
||||
}
|
||||
|
||||
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
|
||||
If ($VMOS -eq "Windows") {
|
||||
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
|
||||
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
|
||||
$vmConfig.OSProfile.ComputerName = $HostName
|
||||
}
|
||||
If ($VMOS -eq "Linux") {
|
||||
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
|
||||
Switch ($VMSubscription) {
|
||||
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
|
||||
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
|
||||
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
|
||||
}
|
||||
}
|
||||
|
||||
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
|
||||
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
|
||||
|
||||
Write-Verbose "Creating VM"
|
||||
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
|
||||
|
||||
Write-Verbose -Message "New-AzVM completed, waiting 60 seconds before checking for completion"
|
||||
Start-Sleep -Seconds 60
|
||||
|
||||
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
|
||||
|
||||
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
|
||||
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$VMEnvironment-") } })
|
||||
|
||||
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
|
||||
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
|
||||
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
|
||||
|
||||
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
|
||||
|
||||
$count = 0
|
||||
|
||||
If ($ExistingDisks) {
|
||||
while ($Size -match $ExistingDisks.DiskSizeGB) {
|
||||
$count++
|
||||
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
|
||||
If ($count -ge 11) {
|
||||
Write-Error "Disk size not available" -ErrorAction Stop
|
||||
}
|
||||
Else {
|
||||
$Size = $Size - 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
|
||||
|
||||
If ($null -ne $DiskDataGB -and $DiskDataGB -gt 0) {
|
||||
$AzureRmDiskConfigParams = @{
|
||||
DiskSizeGB = $DiskDataGB
|
||||
Location = $Location
|
||||
CreateOption = "Empty"
|
||||
SkuName = "Premium_LRS"
|
||||
}
|
||||
|
||||
If ($AvailabilityZone) {
|
||||
Write-Verbose "VM is located in Availability Zone $Zone"
|
||||
$AzureRmDiskConfigParams += @{Zone = $Zone }
|
||||
}
|
||||
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
|
||||
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
|
||||
|
||||
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
|
||||
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
|
||||
|
||||
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
|
||||
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Add networking tag to VM" -Verbose
|
||||
$Tags = @{"Network"="StandardWindows"}
|
||||
New-AzTag -ResourceId $VM.Id -Tag $Tags
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
function New-ITDWindowsVMAzureStep2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$FQDN,
|
||||
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[string]
|
||||
$VMEnvironment,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
$search = @()
|
||||
#$Hostname = $VMName.split('-')[1]
|
||||
$HostName = $FQDN.split('.')[0]
|
||||
###$VMEnvironment = $VMName.split('-')[2] ####
|
||||
|
||||
Write-Verbose -Message ("Searching for Az VM name like " + $HostName)
|
||||
Get-AzSubscription | ForEach-Object {
|
||||
$Subscription = $_
|
||||
Set-AzContext $_ | Out-Null
|
||||
$search += Get-AzVM | Where-Object Name -Like "*$HostName*" | Select-Object *, @{n = 'SubscriptionName'; e = { $Subscription.Name } }
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
switch (@($Search).count) {
|
||||
{ $_ -gt 1 } { Write-Error -Message "More than one virtual machine match found." }
|
||||
{ $_ -lt 1 } { Write-Error -Message "Less than one virtual machine match found." }
|
||||
{ $_ -eq 1 } {
|
||||
Write-Verbose -Message "Prep VM variables"
|
||||
$VMName = $search.Name
|
||||
$ResourceGroupName = $search.ResourceGroupName
|
||||
#$VMEnvironment = $VMName.split('-')[2]
|
||||
$SubscriptionName = $search.SubscriptionName
|
||||
Set-AzContext -Subscription $SubscriptionName
|
||||
|
||||
$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
|
||||
|
||||
Write-Verbose -Message "Set firewall tag"
|
||||
$Tags = @{"Network"="StandardWindows"}
|
||||
New-AzTag -ResourceId $VM.Id -Tag $Tags
|
||||
|
||||
Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization"
|
||||
#Start-Sleep -Seconds 120
|
||||
|
||||
Write-Verbose -Message "Begin Pre-Firewall Guest OS customization"
|
||||
$InvokeAzVMRunCommandParams = @{
|
||||
ResourceGroupName = $ResourceGroupName;
|
||||
Name = $VMName;
|
||||
CommandId = 'RunPowerShellScript'
|
||||
}
|
||||
|
||||
Write-Verbose -Message "1-Set WMI Tags"
|
||||
$ScriptBlock = {
|
||||
try {
|
||||
Write-Verbose "Create new Class"
|
||||
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
|
||||
|
||||
$Class["__CLASS"] = "ITD";
|
||||
$Class.Qualifiers.Add("Static", $true)
|
||||
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
|
||||
|
||||
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
|
||||
|
||||
$Class.Put()
|
||||
|
||||
Write-Verbose "Create single ITD Object"
|
||||
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "3-Disk Configuration"
|
||||
$ScriptBlock = {
|
||||
try {
|
||||
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
|
||||
$disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
|
||||
|
||||
if ($disks) {
|
||||
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
|
||||
|
||||
# Prevent the "You must format this partition before using it." popup
|
||||
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue }
|
||||
|
||||
foreach ($disk in $disks) {
|
||||
if ($disk.IsOffline) {
|
||||
Set-Disk $disk.Number -IsOffline $false
|
||||
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
|
||||
}
|
||||
|
||||
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
|
||||
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
|
||||
|
||||
$diskParam = @{
|
||||
FileSystem = 'NTFS'
|
||||
Confirm = $false
|
||||
}
|
||||
|
||||
$driveLetter = [Int][Char]'D'
|
||||
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
|
||||
$driveLetter++
|
||||
}
|
||||
|
||||
$diskParam.DriveLetter = [Char]$driveLetter
|
||||
|
||||
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Temporary Storage'
|
||||
}
|
||||
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Data'
|
||||
}
|
||||
|
||||
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
|
||||
[void](Format-Volume @diskParam)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "No unpartitioned disks found, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
finally {
|
||||
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "5-Time Zone"
|
||||
$ScriptBlock = {
|
||||
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
|
||||
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time."
|
||||
|
||||
Set-TimeZone -Id 'Central Standard Time'
|
||||
|
||||
Write-Verbose -Message "Time zone set to Central Standard Time."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Time zone is already set to Central Standard Time."
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "6-Enable Performance Counters"
|
||||
$ScriptBlock = {
|
||||
# Enable Performance Counters
|
||||
try {
|
||||
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
|
||||
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
|
||||
|
||||
Write-Verbose -Message "Performance monitors enabled."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Performance monitors already enabled, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
Write-Verbose -Message "7-Disable Windows Firewall"
|
||||
$ScriptBlock = {
|
||||
# Disable Windows Firewall
|
||||
Write-Verbose -Message "Checking for active Windows Firewall..."
|
||||
|
||||
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
|
||||
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
|
||||
|
||||
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
|
||||
|
||||
Write-Verbose -Message "Windows Firewall disabled."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Windows Firewall already disabled, continuing..."
|
||||
}
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
|
||||
|
||||
# determine Active Directory Forest and OU
|
||||
Write-Verbose -Message "8a-Determine domain join"
|
||||
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
|
||||
|
||||
switch ($DomainName) {
|
||||
'nd.gov' {
|
||||
$SearchBaseDomain = "dc=nd,dc=gov"
|
||||
}
|
||||
'ndcloud.gov' {
|
||||
$SearchBaseDomain = "dc=ndcloud,dc=gov"
|
||||
}
|
||||
}
|
||||
|
||||
If ($DomainName -eq "nd.gov") {
|
||||
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
|
||||
If ($null -eq $OUAppName) {
|
||||
Write-Verbose -Message "Dedicated AppName OU for $AppName not found, placing in All-General OU"
|
||||
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
|
||||
}
|
||||
Write-Verbose -Message ("OUAppName: " + $OUAppName.distinguishedName) -Verbose
|
||||
|
||||
# Determine if the Environment sub-ou is special for the selected AppName
|
||||
switch ($OUAppName.Name) {
|
||||
'Shared-PeopleSoft-HigherEd' {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Non-Prod" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
'Shared-PeopleSoft-State' {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Non-Prod" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
Default {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Test" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Gather all sub OUs for the AppName OU
|
||||
Write-Verbose -Message "EnvString is $EnvString" -Verbose
|
||||
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
|
||||
Write-Verbose -Message ("SubOUs:") -Verbose
|
||||
$SubOUs | ForEach-Object { Write-Verbose -Message $_.DistinguishedName -Verbose }
|
||||
$LikeFilter = ("OU=$EnvString,OU=" + $OUAppName.Name + "*")
|
||||
Write-Verbose -Message "LikeFilter: $LikeFilter"
|
||||
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like $LikeFilter }
|
||||
Write-Verbose -Message ("OUToUse: " + $OUToUse.distinguishedName) -Verbose
|
||||
|
||||
<#$MatchFilter = ("OU=$EnvString,OU=" + $OUAppName.Name)
|
||||
Write-Verbose -Message "MatchFiler: $MatchFilter"
|
||||
$OutToUseMatch = ($SubOUs | Where-Object {$_.DistinguishedName -match "^$MatchFilter"})
|
||||
Write-Verbose -Message ("OUToUseMatch: " + $OUToUseMatch.distinguishedName) -Verbose
|
||||
#>
|
||||
# Determine if AD Computer object already exists
|
||||
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
|
||||
If ($ExistingADComputer) {
|
||||
Write-Verbose -Message "AD Object already exists in AD"
|
||||
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
|
||||
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
|
||||
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
|
||||
$AttemptDomainJoin = $true
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
|
||||
$AttemptDomainJoin = $false
|
||||
}
|
||||
}
|
||||
Else {
|
||||
$AttemptDomainJoin = $true
|
||||
}
|
||||
|
||||
If ($AttemptDomainJoin) {
|
||||
$OuFinal = $OUToUse.DistinguishedName
|
||||
Write-Verbose -Message "OuFinal: $OuFinal" -Verbose
|
||||
|
||||
$FirstScriptBlock = { $DomainJoinCred = New-Object System.Management.Automation.PSCredential('svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) }
|
||||
$SecondScriptText = 'Add-Computer -DomainName <DomainName> -OUPath "<OuPath>" -Credential $DomainJoinCred -Server itddc42.nd.gov -Verbose; Restart-Computer -Force -Verbose;'
|
||||
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
|
||||
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
|
||||
|
||||
#Write-Verbose -Message "[$FQDN]:Invoke-VMScript to AD join"
|
||||
$InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")
|
||||
}
|
||||
Write-Verbose -Message $InvokeVMScriptFunc.tostring() -Verbose ###
|
||||
<# wait for firewall and reconnect
|
||||
Write-Verbose -Message "8b-Verify if firewall is open before domain join attempt"
|
||||
$connectivity = $false
|
||||
while ($connectivity -eq $false) {
|
||||
If ( (Test-NetConnection -ComputerName $FQDN).PingSuccedded) {
|
||||
Write-Verbose -Message "Ping successful" -Verbose
|
||||
$connectivity = $true
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Ping failed, may need to wait for PA IP sync" -Verbose
|
||||
Start-Sleep -Seconds 60
|
||||
}
|
||||
}
|
||||
#>
|
||||
Write-Verbose -Message "8c-Attempt domain join"
|
||||
switch ($DomainName) {
|
||||
'nd.gov' {
|
||||
Write-Verbose -Message "Attempting domain join nd.gov"
|
||||
Write-Verbose -Message "Domain: $DomainName"
|
||||
Write-Verbose -Message "OuPath: $OuFinal"
|
||||
<#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptPath 'C:\AzWinBuild\8-Domain-ndgov.ps1' -Parameter @{
|
||||
"DomainName" = 'nd.gov';
|
||||
"OuPath" = "$OuFinal"
|
||||
}#>
|
||||
$ScriptBlock = {
|
||||
Param(
|
||||
[string]
|
||||
$DomainName,
|
||||
|
||||
[string]
|
||||
$OuPath
|
||||
)
|
||||
|
||||
Write-Host $OuPath
|
||||
Test-NetConnection -ComputerName nd.gov
|
||||
|
||||
$DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force))
|
||||
Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred
|
||||
|
||||
Restart-Computer -Force
|
||||
}
|
||||
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{
|
||||
"DomainName" = 'nd.gov';
|
||||
"OuPath" = "$OuFinal"
|
||||
}
|
||||
#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $InvokeVMScriptFunc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+529
@@ -0,0 +1,529 @@
|
||||
<#
|
||||
### step 1
|
||||
gather specs
|
||||
provision virtual machine on hypervisor
|
||||
|
||||
final result:
|
||||
cpu configured, with hot-add
|
||||
memory configured, with hot-add
|
||||
disks 1, 2, and 3 attached with correct sizing
|
||||
network connected
|
||||
virtual machine powered on
|
||||
|
||||
update SCTASK Step 1 completed
|
||||
|
||||
### step 2 - customize Windows Guest OS
|
||||
wait for SCTASK Step 1 completed message
|
||||
configure
|
||||
time zone, mount points, page file, etc
|
||||
|
||||
update SCTASK Step 2 completed
|
||||
|
||||
### step 3 - administration
|
||||
wait for SCTASK Step 2 completed message
|
||||
domain join
|
||||
nd.gov at a minimum
|
||||
install sccm
|
||||
regardless if domain joined
|
||||
|
||||
update SCTASK Step 3 completed
|
||||
|
||||
### step 4 - validation
|
||||
|
||||
|
||||
confirm all agents are installed
|
||||
disks are present and formatted
|
||||
|
||||
#>
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
$NewITDWindowsVmVMwareParams = @{
|
||||
ComputerName = 'itdzmtest300.nd.gov';
|
||||
CPU = 2;
|
||||
MemoryGB = 8;
|
||||
DiskOsGB = 50;
|
||||
DiskSwapGB = 9;
|
||||
DiskDataGB = 20;
|
||||
Subnet = '10.11.12.0/23';
|
||||
OS = 'Windows Server 2022 Datacenter';
|
||||
Environment = "Test";
|
||||
Datacenter = "Bismarck";
|
||||
AppName = "ITD-POC-zmeier";
|
||||
StartupPriority = 4;
|
||||
LicensingRestrictions = "No Licensing Restrictions";
|
||||
Credential = $PrvCred;
|
||||
}
|
||||
|
||||
New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
|
||||
#>
|
||||
|
||||
function New-ITDWindowsVmVMwareStep1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$FQDN,
|
||||
|
||||
[int]
|
||||
$CPU,
|
||||
|
||||
[int]
|
||||
$MemoryGB,
|
||||
|
||||
[int]
|
||||
$DiskOsGB,
|
||||
|
||||
[int]
|
||||
$DiskSwapGB,
|
||||
|
||||
[int]
|
||||
$DiskDataGB,
|
||||
|
||||
[string]
|
||||
$Subnet,
|
||||
|
||||
[string]
|
||||
$OS,
|
||||
|
||||
[string]
|
||||
$VMEnvironment,
|
||||
|
||||
[string]
|
||||
$Datacenter,
|
||||
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[int]
|
||||
$StartupPriority,
|
||||
|
||||
[string]
|
||||
$LicensingRestrictions,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
$FQDN = $FQDN.ToLower()
|
||||
$HostName = $FQDN.split('.')[0]
|
||||
|
||||
If ($VMEnvironment -eq "Development") {
|
||||
$VMEnvironment = "Test"
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Prepare Credentials and Connections"
|
||||
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
|
||||
|
||||
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
|
||||
Write-Verbose -Message "FQDN: $FQDN" -Verbose
|
||||
Write-Verbose -Message "Hostname: $Hostname" -Verbose
|
||||
|
||||
Clear-DnsClientCache
|
||||
$InfobloxVlanMetadata = Get-ITDIbVlan -CIDR $Subnet -Credential $RadiusCred
|
||||
$Cidr = ($InfobloxVlanMetadata.AssignedTo | Out-String).TrimEnd()
|
||||
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
|
||||
#######
|
||||
####### Remove 10.10.10.10 references when DNS sync is fixed
|
||||
#######
|
||||
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction SilentlyContinue -Server 10.10.10.10).IPAddress
|
||||
$SubnetMaskInt = $CIDR.split('/')[1]
|
||||
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
|
||||
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
|
||||
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
|
||||
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
|
||||
([math]::Truncate($Int64 % 256)).ToString()
|
||||
$IPSplit = $Subnet.Split('.')
|
||||
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
|
||||
|
||||
If ($null -ne $IpAddress) {
|
||||
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
|
||||
Write-Verbose -Message "DNS record already exists, CIDR Block match"
|
||||
}
|
||||
Else {
|
||||
Write-Error "DNS record already exists, but does not match CIDR Block"
|
||||
Break
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
|
||||
try {
|
||||
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Verbose -Message ("FQDN is " + $FQDN)
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#######
|
||||
####### Review this code after DNS problems resolved - 2024/09/24 zm
|
||||
#######
|
||||
####### [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
|
||||
|
||||
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
|
||||
|
||||
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
|
||||
Write-Error "IP Address already in use." -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
|
||||
If ($FQDN -like "itdcnd*") {
|
||||
$PasswordStateList = "Peoplesoft Share PW"
|
||||
}
|
||||
Else {
|
||||
$PasswordStateList = "CSRC"
|
||||
}
|
||||
|
||||
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential
|
||||
If ($null -eq $GuestVMLocalCredential) {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
|
||||
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
|
||||
}
|
||||
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
|
||||
Write-Verbose -Message "Decide VMware Cluster and Licensing"
|
||||
switch ($LicensingRestrictions) {
|
||||
"No Licensing Restrictions" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SharePoint Server" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SharePoint Server (Academic)" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL Developer" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL MSDN" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL Standard" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL Standard (Academic)" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL Standard (Vendor Provided)" { $ClusterRoot = "WINDOWS" }
|
||||
"Microsoft SQL Enterprise" { $ClusterRoot = "SQLe" }
|
||||
"Microsoft SQL Enterprise (Academic)" { $ClusterRoot = "SQLa" }
|
||||
"IBM Websphere" { $ClusterRoot = "WAS" }
|
||||
"Powerschool" { $ClusterRoot = "PS" }
|
||||
"Pexip" { $ClusterRoot = "TEL" }
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Decide Datacenter"
|
||||
switch ($Datacenter) {
|
||||
"Bismarck" { $ClusterInt = 1 }
|
||||
"Mandan" { $ClusterInt = 2 }
|
||||
}
|
||||
|
||||
$Cluster = $ClusterRoot + $ClusterInt
|
||||
Write-Verbose -Message "VMware Cluster is $Cluster, gathering metadata for $Cluster"
|
||||
|
||||
switch ($Cluster) {
|
||||
"WINDOWS1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster WINDOWS1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
|
||||
|
||||
If ($LicensingRestrictions -like "*SQL*") {
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_SQL"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
}
|
||||
Else {
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_Gen"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
}
|
||||
}
|
||||
"WINDOWS2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster WINDOWS2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
If ($LicensingRestrictions -like "*SQL*") {
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_SQL"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
}
|
||||
Else {
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_Gen"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
}
|
||||
}
|
||||
"SQLa1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster SQLa1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "SQLa1_FS92_Gen"
|
||||
}
|
||||
"SQLa2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster SQLa2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "SQLa2_FS92_Gen"
|
||||
}
|
||||
"SQLe1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster SQLe1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "SQLe1_FS92_Gen"
|
||||
}
|
||||
"SQLe2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster SQLe2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
|
||||
$DiskStorageFormat = 'EagerZeroedThick'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "SQLe2_FS92_Gen"
|
||||
}
|
||||
"WAS1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster WAS1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WAS1_FS92_Gen"
|
||||
}
|
||||
"WAS2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster WAS2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "WAS2_FS92_Gen"
|
||||
}
|
||||
"PS1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster PS1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "PS1_FS92_Gen"
|
||||
}
|
||||
"PS2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster PS2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "PS2_FS92_Gen"
|
||||
}
|
||||
"TEL1" {
|
||||
$ViServer = 'itdvmvc1.nd.gov'
|
||||
$ComputeCluster = Get-Cluster TEL1
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL1-Data"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "TEL1_FS92_Gen"
|
||||
}
|
||||
"TEL2" {
|
||||
$ViServer = 'itdvmvc2.nd.gov'
|
||||
$ComputeCluster = Get-Cluster TEL2
|
||||
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL2-Data"
|
||||
$DiskStorageFormat = 'Thin'
|
||||
$DatastoreCluster = Get-DatastoreCluster -Name "TEL2_FS92_Gen"
|
||||
}
|
||||
Default {
|
||||
Write-Error "Cluster not found" -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Validate entered disk sizes"
|
||||
Write-Verbose -Message "DiskOsGB is $DiskOsGB. Validating its not a stupid number." -Verbose
|
||||
switch ($DiskOsGB) {
|
||||
{ $_ -lt 50 } {
|
||||
Write-Verbose -Message "DiskOsGB is 0 or below. Since an OS Disk is required, defaulting to 50GB" -Verbose
|
||||
$DiskOsGB = 50
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "DiskSwapGB is $DiskSwapGB. Validating its not a stupid number." -Verbose
|
||||
switch ($DiskSwapGB) {
|
||||
{ $_ -le 0 } {
|
||||
Write-Verbose -Message "DiskSwapGB is zero or below. Since an Swap Disk is required, defaulting to `$Memory + 1GB." -Verbose
|
||||
$DiskSwapGB = ($MemoryGB + 1)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "DiskDataGB is $DiskDataGB. Validating its not a stupid number." -Verbose
|
||||
switch ($DiskDataGB) {
|
||||
{ $_ -le 0 } {
|
||||
Write-Verbose -Message "DiskDataGB is 0. Since an Data Disk is optional, data disk will not be created." -Verbose
|
||||
$DiskDataGB = 0
|
||||
}
|
||||
{ $_ -gt 500 } {
|
||||
Write-Verbose -Message "DiskDataGB is 500GB or more. DiskDataGB will be set to the maximum of 500GB." -Verbose
|
||||
$DiskDataGB = 500
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Determine Datastore / Datastore Cluster"
|
||||
$DiskTotal = $DiskOsGB + $DiskSwapGB + $DiskDataGB
|
||||
If ($DatastoreCluster) {
|
||||
}
|
||||
Else {
|
||||
$DatastoreCluster = Get-DatastoreCluster | Where-Object Name -Like ("*" + $ComputeCluster + "*")
|
||||
}
|
||||
$ClusterDatastoreWithHighestFreeSpaceGB = ($DatastoreCluster | Get-Datastore | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1)
|
||||
If ($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB -gt $DiskTotal) {
|
||||
Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
|
||||
}
|
||||
else {
|
||||
Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will not fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
|
||||
Write-Error ("New VM " + $FQDN + " needs " + $DiskTotal + "GB of free space on a single datastore in the " + $DatastoreCluster.Name + " datastore cluster.") -ErrorAction Stop
|
||||
}
|
||||
|
||||
$FolderLocation = $ComputeCluster | Get-Datacenter | Get-Folder -Name "_New Builds"
|
||||
|
||||
Write-Verbose -Message "Determine VMware VM Template for OS: $OS"
|
||||
switch ($OS) {
|
||||
"Windows Server 2012R2 Standard" { $Template = "Windows Server 2012R2 Standard" }
|
||||
"Windows Server 2016 Standard" { $Template = "Windows Server 2016 Standard" }
|
||||
"Windows Server 2019" { $Template = "Windows Server 2019 Standard 1809.19" }
|
||||
"Windows Server 2019 Standard" { $Template = "Windows Server 2019 Standard 1809.19" }
|
||||
"Windows Server 2019 Datacenter" { $Template = "Windows Server 2019 Standard 1809.19" }
|
||||
"Windows Server 2022" { $Template = "Windows Server 2022 Standard 2108.21" }
|
||||
"Windows Server 2022 Datacenter" { $Template = "Windows Server 2022 Standard 2108.21" }
|
||||
"Windows Server 2025" { $Template = "Windows Server 2025 Standard 24H2.6" }
|
||||
Default { Write-Error "Invalid template option: $OS" -ErrorAction Stop }
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Determine VMware Port Group"
|
||||
$PortGroupsAvailable = Get-VDPortgroup -Server $ViServer -VDSwitch $VirtualSwitch
|
||||
$PortGroup = $PortGroupsAvailable | Where-Object Name -Like ("dvPG_*" + $NetworkId.IPAddressToString + "_" + $SubnetMaskInt)
|
||||
If (!($PortGroup)) {
|
||||
Write-Error "Virtual port group not found" -ErrorAction Stop
|
||||
Stop
|
||||
}
|
||||
If (@($PortGroup).count -gt 1) {
|
||||
Write-Error "Multiple port groups found" -ErrorAction Stop
|
||||
Stop
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Configure Guest OS Customization Spec"
|
||||
$NewOSSpecName = ("AutoBuild-$Hostname-" + (Get-Date -UFormat "%Y%m%d%H%M%S"))
|
||||
Write-Warning "NewOSSpecName = $NewOSSpecName"
|
||||
|
||||
Get-OSCustomizationSpec -Name 'Windows (Auto)' -Server $ViServer | New-OSCustomizationSpec -Name $NewOSSpecName -Type Persistent -Server $ViServer
|
||||
|
||||
Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
|
||||
Set-OSCustomizationSpec `
|
||||
-NamingScheme fixed `
|
||||
-NamingPrefix $Hostname `
|
||||
-AdminPassword $GuestCredentialBB.GetNetworkCredential().Password
|
||||
|
||||
Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
|
||||
Get-OSCustomizationNicMapping | `
|
||||
Set-OSCustomizationNicMapping `
|
||||
-IpMode UseStaticIP `
|
||||
-IpAddress $IpAddress.IPAddressToString `
|
||||
-SubnetMask $SubnetMask.IPAddressToString `
|
||||
-DefaultGateway $DefaultGateway.IPAddressToString `
|
||||
-Dns "10.2.7.40", "10.10.10.10"
|
||||
|
||||
$OSSpec = Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer
|
||||
|
||||
Set-Location C:\Temp
|
||||
$NewVMParams = @{
|
||||
Name = $FQDN;
|
||||
ResourcePool = $ComputeCluster.Name;
|
||||
Datastore = $DatastoreCluster;
|
||||
DiskStorageFormat = $DiskStorageFormat;
|
||||
Location = $FolderLocation;
|
||||
# Removed when using Content Library
|
||||
#Template = $Template;
|
||||
#OSCustomizationSpec = $OSSpec
|
||||
}
|
||||
|
||||
Get-ContentLibraryItem -Name $Template -Server $ViServer | New-VM @NewVMParams
|
||||
$VM = Get-VM -Name $FQDN
|
||||
$VM | Set-VM -OSCustomizationSpec $OSSpec -Confirm:$false
|
||||
|
||||
Write-Verbose -Message "Set vCenter Tags on VM"
|
||||
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category AppName -Name $AppName) -Server $VIServer
|
||||
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category DTAP -Name $VMEnvironment) -Server $VIServer
|
||||
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category LicensingRestrictions -Name $LicensingRestrictions) -Server $VIServer
|
||||
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category StartupPriority -Name $StartupPriority) -Server $VIServer
|
||||
|
||||
|
||||
# Ensure CPU/Memory Hot-Add Enabled
|
||||
$vmView = $VM | Get-View
|
||||
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
|
||||
$vmOptValCPU = New-Object VMware.Vim.OptionValue
|
||||
$vmOptValMem = New-Object VMware.Vim.OptionValue
|
||||
$vmOptValCPU.Key = "vcpu.hotadd"
|
||||
$vmOptValMem.Key = "mem.hotadd"
|
||||
$vmOptValCPU.Value = "true"
|
||||
$vmOptValMem.Value = "true"
|
||||
$vmConfigSpec.ExtraConfig += $vmOptValCPU
|
||||
$vmConfigSpec.ExtraConfig += $vmOptValMem
|
||||
$vmView.ReconfigVM($vmConfigSpec)
|
||||
|
||||
# Set CPU, Memory, Network
|
||||
$VM | Set-VM -NumCpu $CPU -MemoryGB $MemoryGB -Confirm:$false
|
||||
$VM | Get-NetworkAdapter | Set-NetworkAdapter -Portgroup $PortGroup -Confirm:$false
|
||||
|
||||
|
||||
|
||||
Write-Verbose -Message "Config and Update Disks"
|
||||
$VMDisk = $VM | Get-HardDisk
|
||||
$VMDisk1 = $VMDisk | Where-Object Name -EQ "Hard disk 1"
|
||||
$VMDisk2 = $VMDisk | Where-Object Name -EQ "Hard disk 2"
|
||||
$VMDisk3 = $VMDisk | Where-Object Name -EQ "Hard disk 3"
|
||||
|
||||
Write-Verbose -Message "Update Disk 1"
|
||||
$VMDisk1 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 1"
|
||||
If ($VMDisk1.CapacityGB -lt $DiskOsGB) {
|
||||
Set-HardDisk -HardDisk $VMDisk1 -CapacityGB $DiskOsGB -Confirm:$false
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Config Disk 2"
|
||||
If (!$VMDisk2) {
|
||||
$VM | New-HardDisk `
|
||||
-CapacityGB ($MemoryGB + 1) `
|
||||
-StorageFormat Thin `
|
||||
-DiskType Flat `
|
||||
-Persistence Persistent
|
||||
}
|
||||
$VMDisk2 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 2"
|
||||
If ($VMDisk2.CapacityGB -lt $DiskSwapGB) {
|
||||
Set-HardDisk -HardDisk $VMDisk2 -CapacityGB $DiskSwapGB -Confirm:$false
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Config Disk 3"
|
||||
If ($DiskDataGB -gt 0) {
|
||||
Write-Verbose -Message "$DiskDataGB greater than zero"
|
||||
If (!$VMDisk3) {
|
||||
$VM | New-HardDisk `
|
||||
-CapacityGB $DiskDataGB `
|
||||
-StorageFormat Thin `
|
||||
-DiskType Flat `
|
||||
-Persistence Persistent
|
||||
}
|
||||
$VMDisk3 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 3"
|
||||
If ($VMDisk3.CapacityGB -lt $DiskDataGB) {
|
||||
Set-HardDisk -HardDisk $VMDisk3 -CapacityGB $DiskDataGB -Confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Set VMware Tools Upgrade Policy to UpgradeAtPowerCycle"
|
||||
$VMView = $VM | Get-View
|
||||
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
|
||||
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
|
||||
$vmConfigSpec.Tools.ToolsUpgradePolicy = "UpgradeAtPowerCycle"
|
||||
$VMView.ReconfigVM($vmConfigSpec)
|
||||
|
||||
Write-Verbose -Message "Power On VM"
|
||||
$VM | Start-VM
|
||||
|
||||
Write-Verbose -Message "[$FQDN]:Step 1 End"
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+667
@@ -0,0 +1,667 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
$NewITDWindowsVmVMwareParams = @{
|
||||
ComputerName = 'itdzmtest300.nd.gov';
|
||||
Environment = "Test";
|
||||
AppName = "ITD-POC-zmeier";
|
||||
Credential = $PrvCred;
|
||||
}
|
||||
|
||||
New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
|
||||
#>
|
||||
|
||||
function New-ITDWindowsVmVMwareStep2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$FQDN,
|
||||
|
||||
[string]
|
||||
$AppName,
|
||||
|
||||
[string]
|
||||
$VMEnvironment,
|
||||
|
||||
[PSCredential]
|
||||
$Credential
|
||||
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
}
|
||||
|
||||
process {
|
||||
Write-Verbose -Message "Start New-ITDWindowsVmVMwareStep2"
|
||||
$FQDN = $FQDN.ToLower()
|
||||
$HostName = $FQDN.split('.')[0]
|
||||
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
|
||||
|
||||
$VM = Get-VM -Name $FQDN
|
||||
$ViServer = $VM.Uid.split('@')[1].split(':')[0]
|
||||
$VMTagAssignments = Get-TagAssignment -Entity $VM
|
||||
|
||||
#$AppName = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "AppName" }
|
||||
#$DTAP = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "DTAP" }
|
||||
|
||||
Write-Verbose -Message "FQDN: $FQDN"
|
||||
Write-Verbose -Message "HostName: $HostName"
|
||||
|
||||
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
|
||||
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
|
||||
|
||||
If ($null -eq $GuestVMLocalCredential) {
|
||||
Write-Error -Message "Credentials not found in Passwordstate" -ErrorAction Stop
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Credentials found in Passwordstate"
|
||||
Write-Verbose -Message ("username: + " + $GuestVMLocalCredential.UserName)
|
||||
}
|
||||
|
||||
<# Wait for Customization to finish
|
||||
$VMStarted = $false
|
||||
$VMCustomizationStarted = $false
|
||||
$VMCustomizationResult = $false
|
||||
|
||||
While ($VMStarted -eq $false -or $VMCustomizationStarted -eq $false -or $VMCustomizationResult -eq $false) {
|
||||
Write-Warning ("Customization wait loop started " + (Get-Date))
|
||||
Write-Verbose "Current Status:"
|
||||
Write-Verbose ("VMStarted: " + $VMStarted)
|
||||
Write-Verbose ("VMCustomizationStarted: " + $VMCustomizationStarted)
|
||||
Write-Verbose ("VMCustomizationResult: " + $VMCustomizationResult)
|
||||
$GetVIEventRuntime = Measure-Command -Expression { $VMEvents = Get-VIEvent -Entity $VM -Server $ViServer -ErrorAction SilentlyContinue } ## takes a long time to execute
|
||||
Write-Verbose ("Get-VIEvent last run time: " + $GetVIEventRuntime.TotalSeconds + " seconds")
|
||||
If ($VMStarted -eq $false) {
|
||||
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "VMStartingEvent" })) {
|
||||
$VMStarted = $true
|
||||
Write-Warning "[$FQDN]:Virtual machine started"
|
||||
}
|
||||
}
|
||||
If ($VMCustomizationStarted -eq $false) {
|
||||
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationStartedEvent" })) {
|
||||
$VMCustomizationStarted = $true
|
||||
Write-Warning "[$FQDN]:Virtual machine customization started"
|
||||
}
|
||||
}
|
||||
If ($VMCustomizationResult -eq $false) {
|
||||
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationFailed" })) {
|
||||
$VMCustomizationResult = $true
|
||||
Write-Error "[$FQDN]:Virtual machine customization failed"
|
||||
Exit
|
||||
Exit
|
||||
}
|
||||
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationSucceeded" })) {
|
||||
$VMCustomizationResult = $true
|
||||
Write-Warning "[$FQDN]:Virtual machine customization completed"
|
||||
}
|
||||
}
|
||||
} #>
|
||||
|
||||
|
||||
# OS
|
||||
Write-Verbose -Message "Begin Post-Sysprep OS Guest Customization"
|
||||
Write-Verbose -Message "Assigning WMI Tag 000-Prod, SCCM will change it later if required"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Move DVD Drive Mount
|
||||
try {
|
||||
Write-Verbose "Create new Class"
|
||||
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
|
||||
|
||||
$Class["__CLASS"] = "ITD";
|
||||
$Class.Qualifiers.Add("Static", $true)
|
||||
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
|
||||
|
||||
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
|
||||
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
|
||||
|
||||
$Class.Put()
|
||||
|
||||
Write-Verbose "Create single ITD Object"
|
||||
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Checking for DVD drive, and moving it to Z:\"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
Write-Host "hello1"
|
||||
# Move DVD Drive Mount
|
||||
try {
|
||||
$dvd_letter = 'Z'
|
||||
$dvd = Get-WmiObject -Class Win32_Volume -Filter "DriveType=5" | Select-Object -First 1
|
||||
if ($dvd.Name -notmatch $dvd_letter) {
|
||||
Write-Verbose -Message "Found DVD drive, switching to $dvd_letter`:"
|
||||
|
||||
Set-WmiInstance -InputObject $dvd -Arguments @{DriveLetter = "$dvd_letter`:" } | Out-Null
|
||||
|
||||
Write-Verbose -Message "DVD drive moved to drive letter $dvd_letter`:"
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "No DVD drive changes required, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Checking for unpartitioned space on C: volume and expanding"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
Write-Host "hello1"
|
||||
# Expand C: Partition To Maximum Extent
|
||||
try {
|
||||
$cSize = ( Get-Partition -DriveLetter C ).Size
|
||||
$cMaxSize = ( Get-PartitionSupportedSize -DriveLetter C ).SizeMax
|
||||
|
||||
if ($cSize -lt $cMaxSize) {
|
||||
Write-Verbose -Message "Expanding C: from $($csize / 1GB)GB to $($cMaxSize / 1GB)GB..."
|
||||
|
||||
Resize-Partition -DriveLetter C -Size $cMaxSize
|
||||
|
||||
Write-Verbose -Message "C: expanded to $($cMaxSize / 1GB)GB."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "C: is already at maximum size, continuing..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Start Extra Disk(s) config"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Initialize Additional Disks
|
||||
try {
|
||||
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
|
||||
$Disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
|
||||
|
||||
If ($Disks) {
|
||||
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
|
||||
|
||||
ForEach ($disk in $disks) {
|
||||
if ($disk.IsOffline) {
|
||||
Set-Disk $disk.Number -IsOffline $false
|
||||
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
|
||||
}
|
||||
|
||||
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
|
||||
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
|
||||
|
||||
$diskParam = @{
|
||||
FileSystem = 'NTFS'
|
||||
Confirm = $false
|
||||
}
|
||||
|
||||
$driveLetter = [Int][Char]'D'
|
||||
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
|
||||
$driveLetter++
|
||||
}
|
||||
|
||||
$diskParam.DriveLetter = [Char]$driveLetter
|
||||
|
||||
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Temporary Storage'
|
||||
}
|
||||
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
|
||||
$diskParam.NewFileSystemLabel = 'Data'
|
||||
}
|
||||
|
||||
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
|
||||
[void](Format-Volume @diskParam)
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "No unpartitioned disks found, continuing..." -Verbose
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Start Page File Configuration"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Configure Page File
|
||||
if (Get-Partition -DriveLetter D -ErrorAction SilentlyContinue) {
|
||||
Write-Verbose -Message "Setting up pagefile.sys on D:..." -Verbose
|
||||
|
||||
try {
|
||||
if (-not [IO.File]::Exists('D:\pagefile.sys')) {
|
||||
$autoPage = Get-WmiObject -Class Win32_ComputerSystem -EnableAllPrivileges
|
||||
$autoPage.AutomaticManagedPagefile = $false
|
||||
[void]$autoPage.Put()
|
||||
|
||||
Write-Verbose -Message "Disabled automatic pagefile management." -Verbose
|
||||
|
||||
$pageFile = Get-WmiObject -Class Win32_PageFileSetting -EnableAllPrivileges
|
||||
$pageFile.Delete()
|
||||
|
||||
Write-Verbose -Message "Deleted C:\pagefile.sys." -Verbose
|
||||
|
||||
Set-WmiInstance -Class Win32_PageFileSetting -Arguments @{ Name = "D:\pagefile.sys"; InitialSize = 0; MaximumSize = 0; } -EnableAllPrivileges | Out-Null
|
||||
|
||||
Write-Verbose -Message "System managed page file created on D:\pagefile.sys." -Verbose
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Pagefile already configured on D:, continuing..." -Verbose
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Page file drive not found, cannot set up page file. Continuing server configuration..." -Verbose
|
||||
Write-Warning "Page file drive not found, cannot set up page file. Continuing server configuration..."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Enabling Remote Management"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Configure Remote Management (RDP/PoSH)
|
||||
try {
|
||||
Write-Verbose -Message "Checking WinRM..." -Verbose
|
||||
|
||||
if (Test-WSMan -ErrorAction SilentlyContinue) {
|
||||
Write-Verbose -Message "WinRM is already enabled."
|
||||
}
|
||||
else {
|
||||
Enable-PSRemoting -Force
|
||||
|
||||
Write-Verbose -Message "WinRM is now enabled."
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Checking RDP..." -Verbose
|
||||
|
||||
$RDP = Get-WmiObject Win32_TerminalServiceSetting -Namespace root\cimv2\TerminalServices
|
||||
$NLA = Get-WmiObject Win32_TSGeneralSetting -Namespace root\cimv2\TerminalServices -Filter "TerminalName='RDP-tcp'"
|
||||
if ($RDP.AllowTSConnections -eq 0) {
|
||||
Write-Verbose -Message "RDP is disabled, enabling..."
|
||||
|
||||
$RDP.SetAllowTSConnections(1, 1) | Out-Null
|
||||
|
||||
Write-Verbose -Message "RDP is enabled."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "RDP is already enabled, checking NLA security..."
|
||||
}
|
||||
|
||||
if ($NLA.UserAuthenticationRequired -eq 0) {
|
||||
Write-Verbose -Message "RDP is not NLA secured, enabling..."
|
||||
|
||||
$NLA.SetUserAuthenticationRequired(1) | Out-Null
|
||||
|
||||
Write-Verbose -Message "RDP is now NLA secured."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "RDP is already NLA secured."
|
||||
}
|
||||
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Checking current power plan, set to High Performance"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Configure Power Plan
|
||||
try {
|
||||
$powerPlans = powercfg -l
|
||||
|
||||
if ($powerPlans -match '\*$' -notmatch 'High performance') {
|
||||
$currentPlan = [regex]::Match($powerPlans, '(?<=(\())[^)]+(?=(\)\s\*))').Value
|
||||
|
||||
Write-Verbose -Message "Power plan is currently set to $currentPlan, changing to High Performance..." -Verbose
|
||||
|
||||
$highPerformance = [regex]::Match($powerPlans, '([\d\w-\S]+)(?=\s+\(High performance\))').Value
|
||||
[void](powercfg -setactive $highPerformance)
|
||||
|
||||
Write-Verbose -Message "Power plan set to High Performance." -Verbose
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Power plan already configured for High Performance." -Verbose
|
||||
}
|
||||
|
||||
[void](& w32tm.exe /resync /nowait)
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$TimeSyncFunc = {
|
||||
# Configure Time/Date Settings
|
||||
Write-Verbose -Message "Checking current time/date settings..."
|
||||
$Domain = "<DomainName>"
|
||||
try {
|
||||
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
|
||||
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time." -Verbose
|
||||
|
||||
Set-TimeZone -Id 'Central Standard Time'
|
||||
|
||||
Write-Verbose -Message "Time zone set to Central Standard Time." -Verbose
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Time zone is already set to Central Standard Time." -Verbose
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
$TimeSyncScriptBlock = $TimeSyncFunc -replace '<DomainName>', $DomainName
|
||||
$VM | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $TimeSyncScriptBlock
|
||||
#>
|
||||
Write-Verbose -Message "Enable the server manager performance monitors"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Enable Performance Counters
|
||||
|
||||
|
||||
try {
|
||||
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
|
||||
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
|
||||
|
||||
Write-Verbose -Message "Performance monitors enabled." -Verbose
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Performance monitors already enabled, continuing..." -Verbose
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Disable Windows Firewall"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
|
||||
# Disable Windows Firewall
|
||||
Write-Verbose -Message "Checking for active Windows Firewall..." -Verbose
|
||||
|
||||
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
|
||||
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
|
||||
|
||||
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
|
||||
|
||||
Write-Verbose -Message "Windows Firewall disabled." -Verbose
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Windows Firewall already disabled, continuing..." -Verbose
|
||||
}
|
||||
}
|
||||
|
||||
# Active Directory
|
||||
Write-Verbose -Message "Join Active Directory"
|
||||
Write-Verbose -Message "Domain is $DomainName"
|
||||
Write-Verbose -Message "AppName is $AppName"
|
||||
|
||||
switch ($DomainName) {
|
||||
'nd.gov' {
|
||||
$SearchBaseDomain = "dc=nd,dc=gov"
|
||||
}
|
||||
'ndcloud.gov' {
|
||||
$SearchBaseDomain = "dc=ndcloud,dc=gov"
|
||||
}
|
||||
}
|
||||
### Shared-PeopleSoft-HigherEd is not found because of the extra space character in the OU name
|
||||
### commented out section below is the original code, swapped out for new code 2025/01/13
|
||||
|
||||
|
||||
|
||||
# Determine if the AppName has a dedicated OU, and pick it... or choose the All-General OU
|
||||
If ($DomainName -eq 'nd.gov') {
|
||||
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
|
||||
If ($null -eq $OUAppName) {
|
||||
Write-Verbose -Message "Dedicated AppName OU not found, placing in All-General OU"
|
||||
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq "All-General" }
|
||||
}
|
||||
|
||||
# Determine if the Environment sub-ou is special for the selected AppName
|
||||
switch ($OUAppName.Name) {
|
||||
'Shared-PeopleSoft-HigherEd' {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Non-Prod" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
'Shared-PeopleSoft-State' {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Non-Prod" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
Default {
|
||||
switch ($VMEnvironment) {
|
||||
'Test' { $EnvString = "Test" }
|
||||
'Production' { $EnvString = "Prod" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Gather all sub OUs for the AppName OU
|
||||
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
|
||||
If (@($SubOUs).count -eq 1) {
|
||||
$OUToUse = $SubOUs
|
||||
}
|
||||
Else {
|
||||
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like ("OU=$EnvString,OU=" + $OUAppName.Name + "*") }
|
||||
}
|
||||
|
||||
# Determine if AD Computer object already exists
|
||||
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
|
||||
If ($ExistingADComputer) {
|
||||
Write-Verbose -Message "AD Object already exists in AD"
|
||||
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
|
||||
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
|
||||
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
|
||||
$AttemptDomainJoin = $true
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
|
||||
$AttemptDomainJoin = $false
|
||||
}
|
||||
}
|
||||
Else {
|
||||
$AttemptDomainJoin = $true
|
||||
}
|
||||
|
||||
If ($AttemptDomainJoin) {
|
||||
$OuFinal = $OUToUse.DistinguishedName
|
||||
|
||||
$FirstScriptBlock = { $DomainJoinCred = New-Object System.Management.Automation.PSCredential('svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) }
|
||||
$SecondScriptText = 'Add-Computer -DomainName <DomainName> -OUPath "<OuPath>" -Credential $DomainJoinCred -Server itddc42.nd.gov'
|
||||
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
|
||||
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
|
||||
|
||||
Write-Verbose -Message "[$FQDN]:Invoke-VMScript to AD join"
|
||||
$InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")
|
||||
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $InvokeVMScriptFunc
|
||||
|
||||
Write-Warning -Message "[$FQDN]:Restart VMGuest, wait for Tools, then 90 seconds after"
|
||||
Get-VM -Name $FQDN | Restart-VMGuest -Confirm:$false
|
||||
Wait-Tools -VM (Get-VM -Name $FQDN)
|
||||
Start-Sleep -Seconds 90
|
||||
}
|
||||
}
|
||||
else {
|
||||
$GuestCredentialAB = $GuestCredentialBB
|
||||
}
|
||||
<#
|
||||
Write-Verbose -Message ("[$FQDN]:Copying SCCM client installer to C:\temp... " + (Get-Date))
|
||||
|
||||
Copy-VMGuestFile -Source C:\SCCM_Client\ -Destination C:\temp\SCCM_Client -VM (Get-VM -Name $FQDN) -LocalToGuest -GuestCredential $GuestCredentialAB -Force
|
||||
Write-Verbose -Message ("[$FQDN]:SCCM client copy complete " + (Get-Date))
|
||||
#E:\AutoBuild\SCCM_Client\
|
||||
|
||||
# Check if SCCM automatically installed the SCCM client and registered it
|
||||
$CcmRegistered = $false
|
||||
$CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
|
||||
}
|
||||
If ($CcmRegistration.ScriptOutput -match "Client is registered") {
|
||||
Write-Warning "Client is registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
|
||||
Write-Warning "Client is already registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
If ($CcmRegistered -eq $false) {
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
|
||||
If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) {
|
||||
Write-Warning "CCM client is already installing"
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) {
|
||||
Write-Warning "CCM client is already installed"
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
Else {
|
||||
Write-Warning -Message "Installing SCCM Client..."
|
||||
Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "[$FQDN]:Register SCCM Client"
|
||||
While ($CcmRegistered -eq $false) {
|
||||
$CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
|
||||
}
|
||||
If ($CcmRegistration.ScriptOutput -match "Client is registered") {
|
||||
Write-Warning "Client is registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
|
||||
Write-Warning "Client is already registered."
|
||||
$CcmRegistered = $true
|
||||
}
|
||||
ElseIf ($CcmRegistered -eq $false) { Start-Sleep -Seconds 30 }
|
||||
}
|
||||
|
||||
Write-Verbose -Message "[$FQDN]:Approve SCCM Client"
|
||||
#Start-Sleep -Seconds 30 # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL
|
||||
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
|
||||
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
|
||||
$PSDrives = Get-PSDrive
|
||||
If ($PSDrives | Where-Object Name -EQ "ITD") {
|
||||
# ITD Drive exists, do nothing
|
||||
}
|
||||
else {
|
||||
New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov
|
||||
}
|
||||
|
||||
Set-Location ITD:\
|
||||
$Device = Get-CMDevice -Name $args[0]
|
||||
If ($Device.IsApproved -eq 0) {
|
||||
Approve-CMDevice -DeviceName $args[0]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Write-Verbose -Message "Trigger SCCM MachinePolicy First Check-in"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
[void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Trigger SCCM MachinePolicy"
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
[void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
|
||||
}
|
||||
|
||||
<#Write-Verbose -Message "Waiting for network connectivity / Then KVM Activation..."
|
||||
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
|
||||
# Pause until network connectivity is available
|
||||
$KMS = 'kms.nd.gov'
|
||||
|
||||
try {
|
||||
$nwJob = Start-Job -Name 'NetworkCheck' -ScriptBlock {
|
||||
Param ( [String]$KMS )
|
||||
do {
|
||||
$nwStatus = Test-NetConnection -ComputerName $KMS -Port 1688 -InformationLevel Quiet
|
||||
|
||||
Start-Sleep -Seconds 10
|
||||
} until($nwStatus)
|
||||
} -ArgumentList $KMS
|
||||
|
||||
# If after 30 seconds the network connection is not responding continue on
|
||||
if ((Wait-Job -Job $nwJob -Timeout 30).State -eq 'Completed') {
|
||||
Write-Verbose -Message 'Network connectivity has been verified.'
|
||||
}
|
||||
else {
|
||||
[void](Stop-Job -Job $nwJob)
|
||||
Write-Verbose -Message 'Network connectivity could not be verified.'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
|
||||
Activate via KMS
|
||||
Write-Verbose -Message "Activating windows against $KMS..."
|
||||
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit
|
||||
}
|
||||
try {
|
||||
cscript C:\Windows\System32\slmgr.vbs /skms $KMS | Out-Null
|
||||
cscript C:\Windows\System32\slmgr.vbs /ato | Out-Null
|
||||
|
||||
Write-Verbose -Message "Checking activation status..."
|
||||
|
||||
$kmsOut = cscript C:\Windows\System32\slmgr.vbs /dli
|
||||
|
||||
if (($kmsOut | Select-String -Pattern '^License Status:') -match 'Licensed') {
|
||||
Write-Verbose -Message "Windows successfully activated."
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
|
||||
Write-Warning -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Throw $_
|
||||
Break
|
||||
}
|
||||
}
|
||||
#>
|
||||
Write-Verbose -Message ("[$FQDN]:Add to Solarwinds")
|
||||
$Func = {
|
||||
param($C)
|
||||
Import-Module -Name ITDSolarwinds -Verbose
|
||||
Import-SWDiscovery -ComputerName $C -Integration ServiceNow
|
||||
}
|
||||
Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $FQDN -Credential $Credential
|
||||
Write-Verbose -Message "[$FQDN]:End"
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Decomissions a Windows Server device
|
||||
.DESCRIPTION
|
||||
Decomissions a Windows Server device with ITD specifications (DNS/Infoblox, Active Directory, SCCM)
|
||||
.NOTES
|
||||
Credential must be an nd.gov account with access to remove objects on all platforms involved: DNS/Infoblox, Active Directory, SCCM. Read access to vCenter in the future.
|
||||
.EXAMPLE
|
||||
Remove-ITDWindowsServer -ComputerName itdxxx.nd.gov -SCTaskNum SCTASKxxxxxxxxx -Credential $AdminCredential
|
||||
#>
|
||||
|
||||
function Remove-ITDWindowsServer {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]
|
||||
$ComputerName,
|
||||
|
||||
[string]
|
||||
$SCTaskNum,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PSCredential]
|
||||
$Credential
|
||||
)
|
||||
|
||||
begin {
|
||||
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
|
||||
}
|
||||
|
||||
process {
|
||||
$HostName = $ComputerName.split('.')[0]
|
||||
|
||||
# get current user, SCTask, Ritm, custom variables
|
||||
switch ($env:username) {
|
||||
'svcitdiaasauto' {
|
||||
$assignTo = Get-ITDServiceNowUser -Username svcvmwareadm
|
||||
}
|
||||
Default {
|
||||
$assignTo = Get-ITDServiceNowUser -Username $Env:username
|
||||
}
|
||||
}
|
||||
|
||||
$SCTask = Get-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum
|
||||
$RitmNum = $SCTask.request_item.display_value
|
||||
|
||||
Write-Verbose -Message "Gathering $RitmNum"
|
||||
$Ritm = Get-ITDServiceNowRecord -ItemType 'Request Item' -Number $RitmNum -IncludeVariableSet -ErrorAction Stop
|
||||
|
||||
###### the name in the $ComputerName parameter must match a name in the form to continue
|
||||
Write-Verbose -Message "Gathering VariableSet data from $RitmNum"
|
||||
$MatchFound = $false
|
||||
ForEach ($Row in $Ritm.VariableSet) {
|
||||
$TempCi = Get-ITDServiceNowRecord -Table cmdb_ci -SysId ($Row.host_name_ref) -ErrorAction Stop
|
||||
If ($ComputerName -eq $TempCi.FQDN.display_value) {
|
||||
$Ci = $TempCi
|
||||
$MatchFound = $true
|
||||
}
|
||||
}
|
||||
|
||||
$FQDN = $Ci.fqdn.display_value
|
||||
|
||||
If ($MatchFound -eq $false) {
|
||||
Write-Error -Message "ComputerName $ComputerName was not found in VariableSet for $RitmNum" -ErrorAction Stop
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Assigning SCTask to current user, maybe"
|
||||
##### Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{assigned_to = $assignTo.name } REVIEW IF ASSIGNMENT SHOULD BE MADE HERE
|
||||
$short_description = $SCTask.short_description.display_value
|
||||
|
||||
# Gather DNS FQDN and IP
|
||||
$DNSResolve = Resolve-DnsName -Name $ComputerName -ErrorAction SilentlyContinue
|
||||
If ($DNSResolve -eq $null) {
|
||||
$DNSResolve = "DNS object not found"
|
||||
}
|
||||
$DNSInfo = $DNSResolve | ConvertTo-Json -WarningAction SilentlyContinue
|
||||
|
||||
# AD OU
|
||||
$ADComputers = $null
|
||||
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
|
||||
switch ($Domain) {
|
||||
'nd.gov' {
|
||||
try {
|
||||
$ADComputers = Get-ADComputer -Identity $HostName -Properties * -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
# empty because erroraction silentlycontinue doesn't work as expected
|
||||
}
|
||||
If ($ADComputers) {
|
||||
$ADInfo = $ADComputers | ConvertTo-Json -WarningAction SilentlyContinue
|
||||
}
|
||||
Else {
|
||||
$ADInfo = "AD object not found"
|
||||
}
|
||||
}
|
||||
'Default' {
|
||||
Write-Warning -Message "$ComputerName not nd.gov, review other domains manually"
|
||||
}
|
||||
}
|
||||
|
||||
# SCCM collections
|
||||
$SCCMResult = Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
|
||||
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
|
||||
Set-Location ITD:\
|
||||
$CMDevice = Get-CMDevice -Name $args[0]
|
||||
If ($CMDevice) {
|
||||
Get-ITDCMDeviceMemberOf -ComputerName $args[0]
|
||||
}
|
||||
Else {
|
||||
"SCCM object not found"
|
||||
}
|
||||
}
|
||||
$SCCMInfo = $SCCMResult | ConvertTo-Json -WarningAction SilentlyContinue
|
||||
|
||||
Write-Verbose -Message "Updating SCTask with discovered information"
|
||||
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("DNS Information: `n " + $DNSInfo) }
|
||||
|
||||
If ($vCenterInfo) {
|
||||
|
||||
}
|
||||
If ($AzureInfo) {
|
||||
|
||||
}
|
||||
|
||||
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("Active Directory Information: `n " + $ADInfo) }
|
||||
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("SCCM Information: `n " + $SCCMInfo) }
|
||||
|
||||
$CommentsForWorkNotes = $null
|
||||
|
||||
try {
|
||||
$Notes = '<div><strong><span style="color: #c00000;">Decommissioned ' + (Get-Date -UFormat "%Y/%m/%d %H:%M:%S") + '</span></strong></div>'
|
||||
Write-Verbose -Message "All records with Title matching $ComputerName were marked as decommissioned in the Notes field."
|
||||
Update-ITDPassword -Title $ComputerName -AppendNotes -Notes $Notes -Credential $Credential -Force
|
||||
$CommentsForWorkNotes += ("`nPasswordstate: All records with Title matching $ComputerName were marked as decommissioned in the Notes field. ")
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
|
||||
# Power off VM
|
||||
switch ($Ci.model_id.display_value) {
|
||||
{ $_ -like "*VMware*" } {
|
||||
$hardware_platform = "VMware";
|
||||
$hardware_type = 'Virtual Machine'
|
||||
|
||||
$VMs = Get-VM -Name $FQDN -ErrorAction SilentlyContinue | Where-Object { $_.ExtensionData.summary.config.ManagedBy.Type -ne "placeholderVm" }
|
||||
switch ( @($VMs).count ) {
|
||||
{ 0 } {
|
||||
Write-Warning "$FQDN not found in vCenter... is it Azure? Or does it not exist?"
|
||||
}
|
||||
{ $_ -gt 1 } {
|
||||
Write-Verbose -Message "Multiple virtual machines with name $FQDN were found."
|
||||
Write-Error -Message ("Multiple virtual machines with name $FQDN were found. Are there SRM placeholders? If so, unconfigure SRM and run this again. If there are no placeholders, confirm the virtual machine name.") -ErrorAction Stop
|
||||
}
|
||||
1 {
|
||||
Write-Verbose -Message 'One virtual machine with name $FQDN were found.'
|
||||
$TagAssignment = Get-TagAssignment -Entity $VMs
|
||||
$vCenterInfo = $TagAssignment | select Tag, Entity | ConvertTo-Json -Depth 1
|
||||
If ($VMs.PowerState -eq 'PoweredOn') {
|
||||
Write-Verbose -Message "Power off VMware VM $FQDN"
|
||||
$CommentsForWorkNotes += ("`nVMware: VM $FQDN has been powered off. ")
|
||||
$VMs | Stop-VM -Confirm:$false
|
||||
}
|
||||
|
||||
If ($vCenterInfo) {
|
||||
# enter work_notes into sctask
|
||||
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
|
||||
work_notes = ("vCenter Information: `n " + $vCenterInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{ $_ -like "*Microsoft Virtual Machine*" } {
|
||||
$hardware_platform = "Azure";
|
||||
$hardware_type = 'Virtual Machine'
|
||||
}
|
||||
{ $_ -like "*HP*" } {
|
||||
$hardware_platform = 'HPE';
|
||||
$hardware_type = 'Physical'
|
||||
}
|
||||
default { $hardware_platform = 'Other' }
|
||||
}
|
||||
|
||||
# SCCM removal
|
||||
If ($SCCMResult -eq "SCCM object not found") {
|
||||
# do nothing
|
||||
}
|
||||
Else {
|
||||
Write-Verbose -Message "Attempt SCCM removal"
|
||||
try {
|
||||
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
|
||||
switch ($Domain) {
|
||||
'nd.gov' { $SCCMDomain = 'NDGOV' }
|
||||
'itd.nd.gov' { $SCCMDomain = 'NDGOV' }
|
||||
'k12.nd.us' { $SCCMDomain = 'K12' }
|
||||
'stg.k12.nd.us' { $SCCMDomain = 'K12STG' }
|
||||
'ndcloud.gov' { $SCCMDomain = 'NDCLOUD'}
|
||||
}
|
||||
|
||||
Write-Verbose -Message ("$Computername is SCCM Domain $SCCMDomain")
|
||||
|
||||
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname, $SCCMDomain -ScriptBlock {
|
||||
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
|
||||
Set-Location ITD:\
|
||||
$Devices = Get-CMDevice -Name $args[0] | Where-Object Domain -EQ $args[1]
|
||||
$Devices | select Name, Domain
|
||||
ForEach ($Device in $Devices) {
|
||||
If ($Device.Domain -match $SCCMDomain) {
|
||||
Write-Verbose -Message ("SCCM: Removing " + $Device.Name + " on " + $Device.Domain + " domain")
|
||||
$Device | Remove-CMDevice -Confirm:$false -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$CommentsForWorkNotes += ("`nSCCM: Device named $Hostname was removed. ")
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Active Directory removal
|
||||
If ($ADInfo -eq "AD object not found") {
|
||||
# do nothing
|
||||
}
|
||||
Else {
|
||||
try {
|
||||
switch ($Domain) {
|
||||
'nd.gov' {
|
||||
$ADComputers = Get-ADComputer -Identity $HostName
|
||||
switch ( @($ADComputers).count ) {
|
||||
{ 0 } { "AD: Not found" }
|
||||
{ $_ -gt 1 } { "AD: More than one found" }
|
||||
{ 1 } {
|
||||
Write-Verbose -Message "AD: One found, removing"
|
||||
$ADComputers
|
||||
$ADComputers | Remove-ADObject -Recursive -Credential $Credential -Confirm:$false
|
||||
}
|
||||
}
|
||||
$CommentsForWorkNotes += ("`nActive Directory: AD computer object with name $Hostname was removed from the nd.gov domain. ")
|
||||
|
||||
}
|
||||
'Default' {
|
||||
Write-Warning -Message "$ComputerName not nd.gov"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# DNS removal
|
||||
If ($DNSResolve -eq "DNS object not found") {
|
||||
# do nothing
|
||||
}
|
||||
Else {
|
||||
try {
|
||||
Write-Verbose "Attempting DNS removal"
|
||||
Remove-ITDIbDnsRecord -ComputerName $ComputerName -Credential $RadiusCred
|
||||
$CommentsForWorkNotes += ("`nInfoblox: DNS A Record was removed for $ComputerName. ")
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Solarwinds removal
|
||||
try {
|
||||
Remove-ITDSolarwindsNode -ComputerName $ComputerName -Credential $Credential
|
||||
$CommentsForWorkNotes += ("`nSolarwinds: Node was removed for $ComputerName. ")
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
|
||||
$CommentsForWorkNotes += ("`n `nWindows OS Decommission completed. $hardware_platform $hardware_type hardware is ready for removal. ")
|
||||
$HardwareRemovalDescription = ("$ComputerName $hardware_platform $hardware_type hardware is ready for removal.")
|
||||
|
||||
Write-Verbose -Message "Add final comments for work notes into the SCTask, update the short_description for next workflow step"
|
||||
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
|
||||
work_notes = $CommentsForWorkNotes;
|
||||
short_description = $HardwareRemovalDescription;
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# figure out where to put it
|
||||
$x = Get-ADComputer -Filter * -Server itdk12dc7.k12.nd.us -Credential $K12Cred -Properties CanonicalName
|
||||
|
||||
|
||||
|
||||
# enter this on the machine itself
|
||||
$Credential = Get-Credential
|
||||
$AddComputerParams = @{
|
||||
DomainName = 'k12.nd.us';
|
||||
OUPath = 'OU=Prod,OU=All-General,OU=Computers,OU=ITD,DC=k12,DC=nd,DC=us' ;
|
||||
Credential = $Credential;
|
||||
}
|
||||
Add-Computer @AddComputerParams
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user