Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/pull_request_create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ jobs:

- name: Test-ModuleManifest
run: |
Test-ModuleManifest -Path .\PowerAzPlus\PowerAzPlus.psd1
Test-ModuleManifest -Path .\PowerAzPlus.psd1
shell: pwsh

- name: Invoke-ScriptAnalyzer
run: |
$result = Invoke-ScriptAnalyzer -Path .\PowerAzPlus -Recurse -Severity Warning,Error
if ($result.Count -ne 0) { Write-Error $result}
$result = Invoke-ScriptAnalyzer -Path . -Severity Error
if ($result.Count -ne 0) { Write-Output $result; exit 1}
shell: pwsh

- name: Test publish to PowerShell Gallery
run: |
$env:PSGalleryApiKey = "${{ secrets.POWERSHELLGALLERY_API_KEY }}"
Publish-Module -Path ./PowerAzPlus -Repository PSGallery -NuGetApiKey $env:PSGalleryApiKey -WhatIf -Verbose
Publish-Module -Path . -Repository PSGallery -NuGetApiKey $env:PSGalleryApiKey -WhatIf -Verbose
shell: pwsh
2 changes: 1 addition & 1 deletion .github/workflows/pull_request_merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ jobs:
- name: Publish module to PowerShell Gallery
run: |
$env:PSGalleryApiKey = "${{ secrets.POWERSHELLGALLERY_API_KEY }}"
Publish-Module -Path ./PowerAzPlus -Repository PSGallery -NuGetApiKey $env:PSGalleryApiKey
Publish-Module -Path . -Repository PSGallery -NuGetApiKey $env:PSGalleryApiKey
shell: pwsh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testdata/
755 changes: 755 additions & 0 deletions PowerAzPlus-help.xml

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions PowerAzPlus/PowerAzPlus.psd1 → PowerAzPlus.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'PowerAzPlus.psm1'

# Version number of this module.
ModuleVersion = '1.1.2'
ModuleVersion = '1.2.0'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down Expand Up @@ -63,13 +63,16 @@ PowerShellVersion = '5.1'
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
FormatsToProcess = @('.\formats\VnetObjectSortable.format.ps1xml')

# 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 = @('Export-LogicAppDefinition', 'Import-LogicAppDefinition')
FunctionsToExport = @(
'Export-LogicAppDefinition', 'Import-LogicAppDefinition', 'Get-VnetAddressSpace',
'Export-AllKeyVaultSecrets', 'Import-AllKeyVaultSecrets'
)

# 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 = @()
Expand Down
253 changes: 253 additions & 0 deletions PowerAzPlus.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
function Export-LogicAppDefinition {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]
$Name,

[Parameter()]
[ValidateScript(
{
if (Test-Path -Path $_) { $true } else { $false }
},
ErrorMessage = "'{0}' is not a valid path."
)]
[string]
$FilePath = (Get-Location),

[Parameter()]
[ValidatePattern("\.json$", ErrorMessage = "'{0}' should have a .json file extension.")]
[string]
$FileName
)

process {
$logicApp = Get-AzLogicApp -Name $Name

# Creating process-specific variable so it can be nulled out when executing across multiple pipeline inputs
$processFileName = $FileName

# If the LogicApp exists
if ($logicApp) {
Write-Verbose -Message "Processing $Name"

# Creating a process-loop specific variable
if (-not $processFileName) {
$processFileName = "$($Name)_$(Get-Date -Format FileDateTimeUniversal).json"
}

try {
Write-Verbose -Message "Exporting Logic App definition to $processFileName."
$logicApp.Definition.ToString() | Out-File -FilePath "$FilePath\$processFileName" -ErrorAction STOP
$file = Get-Item -Path "$FilePath\$processFileName"

$output = [PSCustomObject]@{
LogicApp = $logicApp.Name
File = $file.FullName
}

$output
}
catch {
$errorMessage = (Get-Error -Newest 1).Exception.Message
Write-Warning -Message "There was an issue exporting the Logic App definition for $Name : $errorMessage"
}
finally {
$processFileName = $null
}
}
else {
Write-Warning -Message "No Logic Apps found named $Name. Double check your spelling or current subscription context using Get-AzContext."
}
} # End process lbock
}

function Import-LogicAppDefinition {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]
$Name,

[Parameter(Mandatory)]
[string]
$ResourceGroupName,

[Parameter(Mandatory)]
[ValidateScript(
{
if (Test-Path -Path $_) { $true } else { $false }
},
ErrorMessage = "'{0}' is not a valid path."
)]
[string]
$FileName
)

Write-Verbose -Message "Processing $Name"

try {
Set-AzLogicApp -Name $Name -ResourceGroupName $ResourceGroupName -DefinitionFilePath $FileName -ErrorAction STOP
}
catch {
$errorMessage = (Get-Error -Newest 1).Exception.Message
Write-Warning -Message "There was an issue importing the Logic App definition for $name : $errorMessage"
}
}

function Get-VnetAddressSpace {
[CmdletBinding(
DefaultParameterSetName = "Subscription"
)]

param(
[Parameter(ParameterSetName = "AllSubscriptions")]
[switch]
$AllSubscriptions,

[Parameter(ParameterSetName = "Subscription")]
[string[]]
$Subscription,

[Parameter()]
[switch]
$SortByIp
)

# Get subscriptions based on parameter set
$allSubs = if ($AllSubscriptions) {
Get-AzSubscription -WarningAction SilentlyContinue | Where-Object State -EQ 'Enabled'
}
else {
$Subscription | ForEach-Object { Get-AzSubscription -SubscriptionId $_ -WarningAction SilentlyContinue }
}

foreach ($sub in $allSubs) {
Set-AzContext -SubscriptionObject $sub | Out-Null

$allVnets = Get-AzVirtualNetwork
$results = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($vnet in $allVnets) {
$addressSpace = $vnet.AddressSpaceText
$allPrefixes = ($addressSpace | ConvertFrom-Json).AddressPrefixes

foreach ($prefix in $allPrefixes) {
$baseAddress, $cidr = $prefix.Split('/')
$octets = $baseAddress -split '\.'

# Convert IP to an integer for sorting
$octetInteger = ([int]$octets[0] * 16777216) + # 256^3
([int]$octets[1] * 65536) + # 256^2
([int]$octets[2] * 256) + # 256^1
([int]$octets[3]) # 256^0

$results.Add(
[PSCustomObject]@{
PSTypeName = "VnetObjectSortable"
VnetName = $vnet.Name
SubscriptionName = $sub.Name
SubscriptionId = $sub.Id
ResourceGroup = $vnet.ResourceGroupName
Location = $vnet.Location
AddressPrefix = $prefix
CIDR = $cidr
IPAddressInteger = $octetInteger
}
) | Out-Null # Suppress output of .Add() to console
}
}
}

# Sort results if the SortByIP switch is used
if ($SortByIP) {
$results = $results | Sort-Object IPAddressInteger
}

$results
}

function Export-AllKeyVaultSecrets {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]
$VaultName,

[Parameter()]
[ValidateScript({
if (Test-Path -Path $_) { return $true }
else { return $false }
})]
[string]
$OutputPath
)

Write-Warning -Message "The target and destination key vaults must use the same subscription and be in an Azure region in the same geography (for example, North America)."

# Gets current location for file exports if not specified as a parameter
if ([String]::IsNullOrEmpty($OutputPath)) {
$OutputPath = Get-Location
}

# Verify valid key vault
$validKeyVault = Get-AzKeyVault -VaultName $VaultName -ErrorAction Stop
if ([string]::IsNullOrEmpty($validKeyVault)) {
throw "Key Vault not found or invalid name: $VaultName."
}

$allSecrets = Get-AzKeyVaultSecret -VaultName $VaultName

if ($allSecrets.Count -gt 0) {
foreach ($secret in $allSecrets) {
$secretName = $secret.Name

# Generate timestamp for filename
$epoch = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$microseconds = [math]::Round(([DateTimeOffset]::UtcNow - (Get-Date "1970-01-01T00:00:00Z")).TotalMicroseconds) % 1000000
$timestamp = "{0}.{1}" -f $epoch, ($microseconds.ToString("000000"))

$fileName = "$OutputPath\\$secretName-$timestamp"

Backup-AzKeyVaultSecret -VaultName $VaultName -Name $secretName -OutputFile $fileName
}
}
else {
Write-Warning -Message "Key Vault $VaultName does not contain any secrets."
}
}

function Import-AllKeyVaultSecrets {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]
$VaultName,

[Parameter(Mandatory)]
[ValidateScript({
if (Test-Path -Path $_) { return $true }
else { return $false }
})]
[string]
$InputFilePath
)

Write-Warning -Message "The target and destination key vaults must use the same subscription and be in an Azure region in the same geography (for example, North America)."

$validKeyVault = Get-AzKeyVault -VaultName $VaultName -ErrorAction Stop
if ([string]::IsNullOrEmpty($validKeyVault)) {
throw "Key Vault not found or invalid name: $VaultName."
}

$backupSecretFiles = Get-ChildItem -Path $InputFilePath

if ($backupSecretFiles.Count -gt 0) {
foreach ($file in $backupSecretFiles) {
Restore-AzKeyVaultSecret -VaultName $VaultName -InputFile $file.FullName
}
}
else {
Write-Warning -Message "No files found in $InputFilePath."
}
}
Loading