git.lirion.de

Of git, get, and gud

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormail_redacted_for_web 2025-07-24 14:49:27 +0200
committermail_redacted_for_web 2025-07-24 14:49:27 +0200
commit1b082205f1e98a084695b042adec5cc122ff7717 (patch)
treeddfc10de4f5a9f7b6732ae98d6d52cf72769f58e
downloadazure-helpers-1b082205f1e98a084695b042adec5cc122ff7717.tar.bz2
InComm, rather spontaneous
-rw-r--r--AzureHelpers/.gitignore9
-rw-r--r--AzureHelpers/AzureHelpers.psd1bin0 -> 868 bytes
-rw-r--r--AzureHelpers/AzureHelpers.psm114
-rw-r--r--AzureHelpers/LICENSE.md157
-rw-r--r--AzureHelpers/Private/vars.ps1.example46
-rw-r--r--AzureHelpers/Public/Find-AzVm.ps169
-rw-r--r--AzureHelpers/Public/Get-AzAccountList.ps122
-rw-r--r--AzureHelpers/Public/List-AzGroups.ps123
-rw-r--r--AzureHelpers/Public/List-AzVms.ps125
-rw-r--r--AzureHelpers/Public/Login-AzSubscription.ps1138
-rw-r--r--AzureHelpers/Public/Show-AzGroup.ps157
-rw-r--r--AzureHelpers/Public/Show-AzVm.ps164
-rw-r--r--AzureHelpers/Public/Start-AzVm.ps144
-rw-r--r--AzureHelpers/Public/Stop-AzVm.ps157
-rw-r--r--AzureHelpers/README.md13
-rw-r--r--AzureHelpers/Tests/AzureHelpers.Tests.ps10
-rw-r--r--AzureHelpers/en-US/about_AzureHelpers.help.txt0
-rw-r--r--LICENSE.md157
-rw-r--r--README.md13
-rw-r--r--install.ps141
-rw-r--r--moduleHelper.ps1225
21 files changed, 1174 insertions, 0 deletions
diff --git a/AzureHelpers/.gitignore b/AzureHelpers/.gitignore
new file mode 100644
index 0000000..33f7783
--- /dev/null
+++ b/AzureHelpers/.gitignore
@@ -0,0 +1,9 @@
+./*
+!./AzureHelpers.psd1
+!./AzureHelpers.psm1
+!./LICENSE.md
+!./.gitignore
+!./en-US/about_AzureHelpers.help.txt
+!./Private/vars.ps1.example
+!./Public/*.ps1
+!./Tests/AzureHelpers.Tests
diff --git a/AzureHelpers/AzureHelpers.psd1 b/AzureHelpers/AzureHelpers.psd1
new file mode 100644
index 0000000..95626b8
--- /dev/null
+++ b/AzureHelpers/AzureHelpers.psd1
Binary files differ
diff --git a/AzureHelpers/AzureHelpers.psm1 b/AzureHelpers/AzureHelpers.psm1
new file mode 100644
index 0000000..d5eac12
--- /dev/null
+++ b/AzureHelpers/AzureHelpers.psm1
@@ -0,0 +1,14 @@
+#!powershell
+# vim:syntax=ps1:ts=4
+# Source functions from elswehere.
+$Private = (Get-ChildItem -Path (Join-Path $PSScriptRoot '.\Private') -Filter *.ps1)
+$Public = (Get-ChildItem -Path (Join-Path $PSScriptRoot '.\Public') -Filter *.ps1 -Recurse)
+
+foreach ($Script in $Public) {
+ . $Script.FullName
+ Export-ModuleMember $Script.BaseName -Alias *
+}
+
+foreach ($Script in $Private) {
+ . $Script.FullName
+} \ No newline at end of file
diff --git a/AzureHelpers/LICENSE.md b/AzureHelpers/LICENSE.md
new file mode 100644
index 0000000..6fb6a01
--- /dev/null
+++ b/AzureHelpers/LICENSE.md
@@ -0,0 +1,157 @@
+# GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+<https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the
+terms and conditions of version 3 of the GNU General Public License,
+supplemented by the additional permissions listed below.
+
+## 0. Additional Definitions.
+
+As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the
+GNU General Public License.
+
+"The Library" refers to a covered work governed by this License, other
+than an Application or a Combined Work as defined below.
+
+An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+## 1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+## 2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+- a) under this License, provided that you make a good faith effort
+ to ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+- b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+## 3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a
+header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+- a) Give prominent notice with each copy of the object code that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+- b) Accompany the object code with a copy of the GNU GPL and this
+ license document.
+
+## 4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken
+together, effectively do not restrict modification of the portions of
+the Library contained in the Combined Work and reverse engineering for
+debugging such modifications, if you also do each of the following:
+
+- a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+- b) Accompany the Combined Work with a copy of the GNU GPL and this
+ license document.
+- c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+- d) Do one of the following:
+ - 0) Convey the Minimal Corresponding Source under the terms of
+ this License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+ - 1) Use a suitable shared library mechanism for linking with
+ the Library. A suitable mechanism is one that (a) uses at run
+ time a copy of the Library already present on the user's
+ computer system, and (b) will operate properly with a modified
+ version of the Library that is interface-compatible with the
+ Linked Version.
+- e) Provide Installation Information, but only if you would
+ otherwise be required to provide such information under section 6
+ of the GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the Application
+ with a modified version of the Linked Version. (If you use option
+ 4d0, the Installation Information must accompany the Minimal
+ Corresponding Source and Corresponding Application Code. If you
+ use option 4d1, you must provide the Installation Information in
+ the manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.)
+
+## 5. Combined Libraries.
+
+You may place library facilities that are a work based on the Library
+side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+- a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities, conveyed under the terms of this License.
+- b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+## 6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+as you received it specifies that a certain numbered version of the
+GNU Lesser General Public License "or any later version" applies to
+it, you have the option of following the terms and conditions either
+of that published version or of any later version published by the
+Free Software Foundation. If the Library as you received it does not
+specify a version number of the GNU Lesser General Public License, you
+may choose any version of the GNU Lesser General Public License ever
+published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/AzureHelpers/Private/vars.ps1.example b/AzureHelpers/Private/vars.ps1.example
new file mode 100644
index 0000000..ba17300
--- /dev/null
+++ b/AzureHelpers/Private/vars.ps1.example
@@ -0,0 +1,46 @@
+#!powershell
+# vim:syntax=ps1:ts=4
+<#
+.SYNOPSIS
+
+Define variables for the functions defined mainly in /public
+
+.OUTPUTS
+
+[Hashtable]$tenantMap Maps desired names to a subscription's tenant UUID
+
+[Hashtable]$subscrMap Maps desired names to a subscription's own UUID
+#>
+# For now, we have to do these manually.
+# For every subscription you want to use with Login-AzSubscription,
+# add the name in the enum and the tenant UUID in the tenant map as well
+# as the subscription UUID in the subscription map.
+# Here, you do it once. Better than every time you switch subscriptions :-)
+
+
+# NOT USED ANYMORE. The validation we can do through this is outweighed by
+# users having to populate only simple variables below. Plus, we have to
+# check whether the maps contain the key in any case and do that already.
+# The enum would make more sense in case of a centralised output, which is why
+# we keep this commented out.
+# # The names you want to use for the subscriptions. Does not have to match the
+# # actual names in Azure - use simplified names if you like to.
+# Add-Type -TypeDefinition @"
+# public enum azSubscriptions {
+ # common,
+ # customer1
+ # }
+# "@
+
+# The tenants that the subscriptions have been created in. Again, use whatever
+# name - the UUIDs are important.
+$tenantMap = @{
+ 'common' = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ 'customer1' = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
+}
+
+# The subscription UUIDs themselves. Again, only the UUIDs must match Azure.
+$subscrMap = @{
+ 'common' = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ 'customer1' = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Find-AzVm.ps1 b/AzureHelpers/Public/Find-AzVm.ps1
new file mode 100644
index 0000000..73467c0
--- /dev/null
+++ b/AzureHelpers/Public/Find-AzVm.ps1
@@ -0,0 +1,69 @@
+function Find-AzVm {
+ <#
+ .SYNOPSIS
+ Return an Azure VM object if we find exactly one match for a given VM name string.
+
+ .DESCRIPTION
+ We'll search for all VMs inside the subsctription we are logged into containing
+ the input string. If we find exactly one match, we will output an object as
+ delivered by `az vm list`.
+
+ .EXAMPLE
+ Find-AzVm -VmName "substring-01"
+
+ .INPUTS
+ String. The VM name we want to investigate. Part of the name is sufficient if
+ unambiguous inside the active subscription.
+
+ .OUTPUTS
+ PSCustomObject. `az vm list` object of the resulting Azure virtual machine.
+ #>
+ [OutputType([PSCustomObject])]
+ [Alias(
+ 'azvmidentify',
+ 'azvmf',
+ 'azvmi'
+ )]
+ Param (
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipeline=$true,
+ HelpMessage="String that is a VM name or is part of one unambiguous VM",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $VmName
+ )
+ $ErrorActionPreference = 'Stop'
+ $count = 0
+ $result = @()
+ foreach ($myvm in (List-AzVms | ConvertFrom-Json) ) {
+ if ($myvm.name.Contains($VmName)) {
+ $count++
+ $result += $myvm
+ }
+ }
+ switch ($result.Count) {
+ 0 {
+ throw [System.ArgumentNullException]::New("No VM found with its name containing `"$($VmName)`"")
+ }
+ 1 {
+ $true | Out-Null
+ }
+ Default {
+ throw [System.ArgumentException]::New("More than one VM found with their names containing `"$($VmName)`"")
+ }
+ }
+ # We throw exceptions whenever we don't have exactly one element - here we return the first element as there
+ # should be only one at this stage.
+ # Also, Powershell (again!): If you "return" something from a function anything else that produces output
+ # will also be returned. Why, Microsoft, why ლ(ಠ_ಠლ)
+ # > PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around:
+ # > * All output is captured, and returned
+ # > * The return keyword really just indicates a logical exit point
+ # ( https://stackoverflow.com/a/10288256 )
+ # This sorry state of a "shell" or "programming language" ...
+ return $result[0]
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Get-AzAccountList.ps1 b/AzureHelpers/Public/Get-AzAccountList.ps1
new file mode 100644
index 0000000..d49d8e4
--- /dev/null
+++ b/AzureHelpers/Public/Get-AzAccountList.ps1
@@ -0,0 +1,22 @@
+function Get-AzAccountList {
+ <#
+ .SYNOPSIS
+ Show details about all subscriptions the Azure account we are logged into has access to.
+
+ .INPUTS
+ None.
+
+ .OUTPUTS
+ String. A coloured JSON output showing a more or less terse list of subscriptions.
+ #>
+ [Alias(
+ 'getazacclist',
+ 'azacclist',
+ 'azalist'
+ )]
+ # PowerShell will throw an exception "Unexpected attribute 'Alias'." if you don't define Param() below. If you do, everything is fine.
+ # POWERSHELL IS SO SOPHISTICATED AND GOOD, the number of times I've heard this bollocks definitely equals the quality
+ Param(
+ )
+ az account list -o jsonc --query '[].{name: name, id: id, state: state, homeTenantId: homeTenantId, tenantId: tenantId, user: user}'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/List-AzGroups.ps1 b/AzureHelpers/Public/List-AzGroups.ps1
new file mode 100644
index 0000000..a280388
--- /dev/null
+++ b/AzureHelpers/Public/List-AzGroups.ps1
@@ -0,0 +1,23 @@
+function List-AzGroups {
+ <#
+ .SYNOPSIS
+ List all resource groups inside the Azure subscription we are logged into.
+
+ .DESCRIPTION
+ This simply uses `az group list` with a few parameters. Main purpose: coloured
+ and terse output.
+
+ .INPUTS
+ None. (Also, no parameters.)
+
+ .OUTPUTS
+ String. A coloured JSON output showing a more or less terse list of VMs.
+ #>
+ [Alias(
+ 'azgrouplist',
+ 'azrgl'
+ )]
+ Param (
+ )
+ az group list -o jsonc --query '[].{id: id, location: location, managedBy: managedBy, properties: properties, tags: tags}'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/List-AzVms.ps1 b/AzureHelpers/Public/List-AzVms.ps1
new file mode 100644
index 0000000..8c88a63
--- /dev/null
+++ b/AzureHelpers/Public/List-AzVms.ps1
@@ -0,0 +1,25 @@
+function List-AzVms {
+ <#
+ .SYNOPSIS
+ List all VMs inside the Azure subscription we are logged into.
+
+ .DESCRIPTION
+ This simply uses `az vm list` with a few parameters. Main purpose: coloured
+ and terse output.
+
+ .INPUTS
+ None. (Also, no parameters.)
+
+ .OUTPUTS
+ String. A coloured JSON output showing a more or less terse list of VMs.
+ #>
+ [Alias(
+ 'azvmlist',
+ 'azvml'
+ )]
+ # PowerShell will throw an exception "Unexpected attribute 'Alias'." if you don't define Param() below. If you do, everything is fine.
+ # POWERSHELL IS SO SOPHISTICATED AND GOOD, the number of times I've heard this bollocks definitely equals the quality
+ Param(
+ )
+ az vm list -o jsonc --query '[].{name: name,resourceGroup: resourceGroup,tenantId: identity.tenantId,principalId: identity.principalId}'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Login-AzSubscription.ps1 b/AzureHelpers/Public/Login-AzSubscription.ps1
new file mode 100644
index 0000000..b02229f
--- /dev/null
+++ b/AzureHelpers/Public/Login-AzSubscription.ps1
@@ -0,0 +1,138 @@
+function Login-AzSubscription {
+ <#
+ .SYNOPSIS
+ Log in to a predefined set of tenants+subscription combinations.
+
+ .DESCRIPTION
+ The sets define abstract subscription "shortnames" which will also
+ yield its tenant. To find out which just use tabulator expansion.
+
+ This function eases the login procedure. While Azure and AzureCLI
+ are still beta implementations, we need to work around a lot of
+ stuff, which is why this function has grown quite some in size.
+ Its usage will remain easy, however :-)
+
+ We expect ./private/vars.ps1 to be populated. It consists of:
+ - [Hashtable]$tenantMap = @{ subscrName = tenantUUID; ... }
+ - [Hashtable]$subscrMap = @{ subscrName = subscriptionUUID; ... }
+ (subscrName is a name chosen by you, it can be the actual name or
+ some mnemonic, whatever you prefer. The UUID(GUID) is the one in Azure.)
+
+ .EXAMPLE
+ Login-AzSubscription mysubscription
+
+ .PARAMETER subscrName
+ The subscription name. Its mapping has to exist in ./private/vars.ps1
+ (the module containing this function is delivered with an example).
+ #>
+ [Alias(
+ 'Login-AzTenant',
+ 'azlogin'
+ )]
+ Param (
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipeline=$true,
+ HelpMessage="The name (our alias) of the subscription you intend to login to.",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ # [azSubscriptions]
+ $subscrName
+ # $subscrEnum
+ )
+ # # Since PowerShell Enum doesn't give a fuck about sub-types, we better cast to string here.
+ # # Before, we tried to access $tenantMap[$subscrEnum] below and failed, so better keep it that way :-)
+ # $subscrName = [String]$subscrEnum
+ [regex]$uuidRex = '(?im)^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$'
+ if ( -not $tenantMap.Contains($subscrName) ) {
+ Write-Host "Exception. Did you populate /private/vars.ps1?"
+ throw [System.ArgumentException]::New("Known tenants do not include `"$($SubscrName)`"!")
+ }
+ if ( -not $subscrMap.Contains($subscrName) ) {
+ Write-Host "Exception. Did you populate /private/vars.ps1?"
+ throw [System.ArgumentException]::New("Known subscriptionss do not include `"$($SubscrName)`"!")
+ }
+ # PowerShell again: The below simple statement in any other language would work. Here you receive
+ # "System.Collections.Hashtable[learn]" instead of the value string assigned to key $TenantName:
+ # az login --tenant $tenantMap[$TenantName]
+ # ...here, we need an additional detour and assignment (WiP):
+ $tuuid = [String]$tenantMap[$subscrName]
+ if ( -not ($tuuid -match $uuidRex) ) {
+ # PowerShell again. Cannot just combine strings with strings directly (like "value of variable: ${variable}, right here").
+ # Most languages can. PS can't.
+ $throwstr = "Tenant ID string is not a UUID! ("
+ $throwstr += $tuuid
+ $throwstr += ")"
+ throw [System.ArgumentException]::New($throwstr)
+ }
+ $suuid = [String]$subscrMap[$subscrName]
+ if ( -not ($suuid -match $uuidRex) ) {
+ # PowerShell again. Cannot just combine strings with strings directly (like "value of variable: ${variable}, right here").
+ # Most languages can. PS can't.
+ $throwstr = "Subscription ID string is not a UUID! ("
+ $throwstr += $suuid
+ $throwstr += ")"
+ throw [System.ArgumentException]::New($throwstr)
+ }
+ if ( ((az account list --only-show-errors -o json) | ConvertFrom-Json).Count -ne 0 ) {
+ Write-Host "Already logged in to an Azure subscription."
+ # For some very odd reason (bad coding in PS?* :-) ), this output would appear LAST. meaning after the last call of the function. (wtf?)
+ # * or do the round brackets do some kind of subshelling? other than what people told me? to be researched.
+ # (az account show --only-show-errors -o json | ConvertFrom-Json) | Select-Object tenantId,name
+ # ---
+ # Here's the thing with AzureCLI as a whole: Microsoft don't even adhere to their own frickin standards. If "az something"* fails, they won't throw an exception, and
+ # "az account" does not know about "-ErrorAction" (which is a de-facto standard!). Finally, PowerShell itself does not use error codes like everybody else, so you're stuck
+ # on some output to interpret. Morons. We hence just try to assign a variable to this command, and if the variable is empty, tadaaa error. Lel.
+ # * no, seriously, try "az something". "something" is unknown, and even then there is no darn exception**, just red text. No try/catch possible. Hilarious.
+ # ** Even better, "az something" does not complain about "-ErrorAction", it simply ignores it in this case. Absolute pros at work :D ("az account" at least states it does not adhere to that...)
+ $mytok = (az account get-access-token -o json 2>$null | ConvertFrom-Json )
+ if ( $mytok -eq $null ) {
+ [Console]::ForegroundColor = 'red'
+ Write-Host "Token possibly invalid, AzureCLI has no means of renewing tokens (sic),"
+ Write-Host "hence logging you out."
+ [Console]::ResetColor()
+ az logout
+ Write-Host "...done. Please log in again."
+ } else {
+ # Do not have sensitive data lingering about:
+ Clear-Variable mytok
+ # Set subscription:
+ Write-Host "Setting active subscription to $($suuid)...`n"
+ az account set -o jsonc --subscription $suuid
+ }
+ } else {
+ $loginex = $("" | Out-String)
+ $loginex += "Not logged in, trying to log in.`n"
+ Write-Host $loginex
+ Write-Host "Tenant: $tuuid"
+ Write-Host "Subscription: `"$SubscrName`" = $tuuid"
+ if ( $tuuid -ne $null ) {
+ # TODO: research --use-device-code
+ # We cannot log in stating we are azure user user@whatnot, this would lead to not using MFA.
+ # Great design, Microsoft, I just want to enter my creds but not the frickin username every time. -.-'
+ # (Also, what about user-based vaulting (incl. an Enterprise Vault) and then using a TOTP?
+ # Microsoft won't even do a proper TOTP, they hide that behind their "Authenticator" (which is EFFING MANDATORY).
+ # Also, Microsoft take a huge dump on established standards like the aforementioned, or browser plugins for vaults.
+ # And many people still think this is good design. ಠ_ಠ (Do you?)
+ # )
+ # az login --username (Get-ConsolutAzLoginName) --tenant $tuuid
+ # ...so back to using that UI designed for noobs (its existence isn't bad - dictating it is, and makes devs more inefficient.)
+ # TODO: Can we work around with managed identities? And should we consider this at all, from a security perspective?
+ Write-Host "Logging in..."
+ az login -o jsonc --tenant $tuuid
+ Write-Host "...logged in."
+ Write-Host "Setting active subscription to $($suuid)..."
+ az account set -o jsonc --subscription $suuid
+ } else {
+ # PowerShell is just sitting on the zombie hydra that .NET is. --> https://powershellexplained.com/2017-04-07-all-dotnet-exception-list/#systemargumentnullexception
+ throw [System.ArgumentNullException]::New("UUID for tenant not found: $SubscrName")
+ }
+ }
+ Write-Host "Access token:"
+ az account get-access-token -o jsonc --query '{expiresOn: expiresOn, expires_on: expires_on, subscription: subscription, tenant: tenant, tokenType: tokenType}'
+ Write-Host "Active subscription:"
+ az account show -o jsonc --query '{name: name, id: id, tenantId: tenantId, user: user}'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Show-AzGroup.ps1 b/AzureHelpers/Public/Show-AzGroup.ps1
new file mode 100644
index 0000000..7de8bfa
--- /dev/null
+++ b/AzureHelpers/Public/Show-AzGroup.ps1
@@ -0,0 +1,57 @@
+function Show-AzGroup {
+ <#
+ .SYNOPSIS
+ List details of an Azure resource group containing an input string.
+
+ .DESCRIPTION
+ We'll find out all resource group inside the subscription we are logged into
+ which contain the string $GroupName (i.e. 'yGrou' will yield 'myGroup').
+
+ If we find more than one result, we will throw an exception telling this.
+
+ If there is one match, a more or less terse output will be generated displaying
+ the group details.
+
+ .INPUTS
+ String. The resource group name we want to investigate. Part of the name is
+ sufficient if unambiguous inside the active subscription.
+
+ .OUTPUTS
+ String. A coloured JSON output showing a more or less terse list of
+ the resource group's parameters.
+ #>
+ [Alias(
+ 'azgroup',
+ 'azrg'
+ )]
+ Param(
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipeline=$true,
+ HelpMessage="String that is a resource group name or is part of one unambiguous RG",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $GroupName
+ )
+ $groups = @()
+ foreach ($group in (List-AzGroups)) {
+ if ($group.name.Contains($GroupName)) {
+ $groups += $group
+ }
+ }
+ switch ($groups.Count) {
+ 0 {
+ throw [System.ArgumentNullException]::New("No resource group found with its name containing `"$($GroupName)`"")
+ }
+ 1 {
+ $true | Out-Null
+ }
+ Default {
+ throw [System.ArgumentException]::New("More than one resource group found with their names containing `"$($GroupName)`"")
+ }
+ }
+ az group show -g $group[0].name --query '{id: id, location: location, managedBy: managedBy, properties: properties, tags: tags}'
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Show-AzVm.ps1 b/AzureHelpers/Public/Show-AzVm.ps1
new file mode 100644
index 0000000..6021015
--- /dev/null
+++ b/AzureHelpers/Public/Show-AzVm.ps1
@@ -0,0 +1,64 @@
+function Show-AzVm {
+ <#
+ .SYNOPSIS
+ List all details of an Azure VM whose name contains an input string.
+
+ .DESCRIPTION
+ We'll find out all VMs inside the subscription we are logged into
+ which contain the string $VmName (i.e. 'yVirtua' will yield 'myVirtualMachine').
+
+ If we find more than one result, we will throw an exception telling this.
+
+ If there is one match, a more or less terse output will be generated displaying
+ the VM details.
+
+ .INPUTS
+ String. The VM name we want to investigate. Part of the name is sufficient if
+ unambiguous inside the active subscription.
+
+ .OUTPUTS
+ String. A coloured JSON output showing a more or less terse list of
+ the virtual machine's parameters.
+ #>
+ [Alias(
+ 'azvmdeets',
+ 'azvmdetails',
+ 'azvmd'
+ )]
+ Param(
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipeline=$true,
+ HelpMessage="Exact name of a VM",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $VmName
+ )
+ foreach ($myvm in $VmName) {
+ # az vm list -d -o jsonc --query "[?name == `'$myvm`']" `
+ # | ConvertFrom-Json `
+ # | Select-Object name,powerState,privateIps,publicIps,resourceGroup,tags
+ # Something like a zone also doesn't come with list -d, we need show for THAT,
+ # and show needs the resource group.
+ # also, az vm list -d is raging slow (5+ seconds at times).
+ # ALSO, az vm show -d is raging slow.
+ # We need one of these to see BASIC stuff like IPs or power state. Cheerio.
+ # So: list the VM with the name, extract resource group, switch to az vm show.
+ $resvms = (az vm list -d -o jsonc --query "[?name == `'$myvm`']" | ConvertFrom-Json) `
+ | Select-Object name,resourceGroup,powerState,privateIps,publicIps
+ if ( [String]$resvms.GetType() -ne 'System.Management.Automation.PSCustomObject' ) {
+ foreach ($resvm in $resvms) {
+ az vm show -d -g $resvm.resourceGroup -n $resvm.name -o jsonc `
+ --query '{name: name, id: id, powerState: powerState, resourceGroup: resourceGroup, zones: zones, privateIps: privateIps, publicIps: publicIps, tags: tags}'
+ # $resvm | Select-Object powerState,privateIps,publicIps
+ }
+ } else {
+ az vm show -d -g $resvms.resourceGroup -n $resvms.name -o jsonc `
+ --query '{name: name, id: id, powerState: powerState, resourceGroup: resourceGroup, zones: zones, privateIps: privateIps, publicIps: publicIps, tags: tags}'
+ # $resvms | Select-Object powerState,privateIps,publicIps
+ }
+ }
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Start-AzVm.ps1 b/AzureHelpers/Public/Start-AzVm.ps1
new file mode 100644
index 0000000..408fb0c
--- /dev/null
+++ b/AzureHelpers/Public/Start-AzVm.ps1
@@ -0,0 +1,44 @@
+function Start-AzVm {
+ # Supply a VM name (or an unambiguous part of it) and start the machine.
+ <#
+ .SYNOPSIS
+ Start an Azure VM whose name contains the input string.
+
+ .DESCRIPTION
+ If we can unambiguously determine a single VM name inside the subscription
+ we are logged into through the input string, we will stop and deallocate
+ the machine.
+
+ This function was built as we want to have terse input and a counterpart
+ to Stop-AzVm.
+
+ .INPUTS
+ String. The name or unambiguous part of the name of the VM we intend to
+ start.
+
+ .OUTPUTS
+ String. Status messages and the actual AzureCLI outputs.
+ #>
+ Param (
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="String that is a VM name or is part of one unambiguous VM",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $VmName
+ )
+ $ErrorActionPreference = 'Stop'
+ $myvm = azvmidentify -VmName $VmName
+ # Since az vm start is taking quite its time even when the machine is started, we should check ourselves whether the machine is running:
+ if ( (az vm show -d -g $myvm.resourceGroup -n $myvm.name -o json | COnvertFrom-Json).powerState.Contains('running')) {
+ Write-Host "VM $($myvm.name) (RG: $($myvm.resourceGroup)) is already running."
+ } else {
+ Write-Host "Starting $($myvm.name) (RG: $($myvm.resourceGroup)):"
+ az vm start -g $myvm.resourceGroup -n $myvm.name
+ Write-Host "...done."
+ }
+} \ No newline at end of file
diff --git a/AzureHelpers/Public/Stop-AzVm.ps1 b/AzureHelpers/Public/Stop-AzVm.ps1
new file mode 100644
index 0000000..1b3bd07
--- /dev/null
+++ b/AzureHelpers/Public/Stop-AzVm.ps1
@@ -0,0 +1,57 @@
+function Stop-AzVm {
+ # Supply a VM name (or an unambiguous part of it), stop, and deallocate the machine.
+ <#
+ .SYNOPSIS
+ Stop **and** deallocate an Azure VM whose name contains the input string.
+
+ .DESCRIPTION
+ If we can unambiguously determine a single VM name inside the subscription
+ we are logged into through the input string, we will stop and deallocate
+ the machine.
+
+ This function was built as we want to have terse input and we do not want to
+ be billed for machines we stop, the latter requiring two actual commands.
+
+ .INPUTS
+ String. The name or unambiguous part of the name of the VM we intend to
+ stop and deallocate.
+
+ .OUTPUTS
+ String. Status messages and the actual AzureCLI outputs.
+ #>
+ [Alias(
+ 'azvmstop',
+ 'azvmd',
+ 'Deallocate-AzVm'
+ )]
+ Param (
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="String that is a VM name or is part of one unambiguous VM",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $VmName
+ )
+ $ErrorActionPreference = 'Stop'
+ $myvm = azvmidentify -VmName $VmName
+ # Since az vm stop is taking quite its time even when the machine is started, we should check ourselves whether the machine is running:
+ $myPowerState = (az vm show -d -g $myvm.resourceGroup -n $myvm.name -o json | ConvertFrom-Json).powerState
+ if ( ($myPowerState -match '(stopped|deallocated)$') ) {
+ Write-Host "VM $($myvm.name) (RG: $($myvm.resourceGroup)) is already stopped."
+ } else {
+ Write-Host "Stopping $($myvm.name) (RG: $($myvm.resourceGroup)):"
+ az vm stop -g $myvm.resourceGroup -n $myvm.name
+ Write-Host "...done."
+ }
+ if ( ($myPowerState -match 'deallocated$') ) {
+ Write-Host "VM $($myvm.name) (RG: $($myvm.resourceGroup)) is already deallocated."
+ } else {
+ Write-Host "Deallocating $($myvm.name) (RG: $($myvm.resourceGroup)):"
+ az vm deallocate -g $myvm.resourceGroup -n $myvm.name
+ Write-Host "...done."
+ }
+} \ No newline at end of file
diff --git a/AzureHelpers/README.md b/AzureHelpers/README.md
new file mode 100644
index 0000000..89aaa19
--- /dev/null
+++ b/AzureHelpers/README.md
@@ -0,0 +1,13 @@
+# About
+
+Currently, I have to work with Azure besides seasoned virtualisation.
+
+These is a growing collection of helpers I use. It will grow with my
+understanding and needs with this... product.
+
+# Location
+
+If you find this on GitHub - this is a fork from outside GitHub. Its
+upstream is on a non-Microsoft git. (Details may follow - right now
+it's on a public but privately maintained git server, it may be migrated
+to a public FOSS instance like Codeberg or whatnot.) \ No newline at end of file
diff --git a/AzureHelpers/Tests/AzureHelpers.Tests.ps1 b/AzureHelpers/Tests/AzureHelpers.Tests.ps1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/AzureHelpers/Tests/AzureHelpers.Tests.ps1
diff --git a/AzureHelpers/en-US/about_AzureHelpers.help.txt b/AzureHelpers/en-US/about_AzureHelpers.help.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/AzureHelpers/en-US/about_AzureHelpers.help.txt
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..6fb6a01
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,157 @@
+# GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+<https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the
+terms and conditions of version 3 of the GNU General Public License,
+supplemented by the additional permissions listed below.
+
+## 0. Additional Definitions.
+
+As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the
+GNU General Public License.
+
+"The Library" refers to a covered work governed by this License, other
+than an Application or a Combined Work as defined below.
+
+An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+## 1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+## 2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+- a) under this License, provided that you make a good faith effort
+ to ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+- b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+## 3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a
+header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+- a) Give prominent notice with each copy of the object code that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+- b) Accompany the object code with a copy of the GNU GPL and this
+ license document.
+
+## 4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken
+together, effectively do not restrict modification of the portions of
+the Library contained in the Combined Work and reverse engineering for
+debugging such modifications, if you also do each of the following:
+
+- a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+- b) Accompany the Combined Work with a copy of the GNU GPL and this
+ license document.
+- c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+- d) Do one of the following:
+ - 0) Convey the Minimal Corresponding Source under the terms of
+ this License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+ - 1) Use a suitable shared library mechanism for linking with
+ the Library. A suitable mechanism is one that (a) uses at run
+ time a copy of the Library already present on the user's
+ computer system, and (b) will operate properly with a modified
+ version of the Library that is interface-compatible with the
+ Linked Version.
+- e) Provide Installation Information, but only if you would
+ otherwise be required to provide such information under section 6
+ of the GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the Application
+ with a modified version of the Linked Version. (If you use option
+ 4d0, the Installation Information must accompany the Minimal
+ Corresponding Source and Corresponding Application Code. If you
+ use option 4d1, you must provide the Installation Information in
+ the manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.)
+
+## 5. Combined Libraries.
+
+You may place library facilities that are a work based on the Library
+side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+- a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities, conveyed under the terms of this License.
+- b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+## 6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+as you received it specifies that a certain numbered version of the
+GNU Lesser General Public License "or any later version" applies to
+it, you have the option of following the terms and conditions either
+of that published version or of any later version published by the
+Free Software Foundation. If the Library as you received it does not
+specify a version number of the GNU Lesser General Public License, you
+may choose any version of the GNU Lesser General Public License ever
+published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..89aaa19
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# About
+
+Currently, I have to work with Azure besides seasoned virtualisation.
+
+These is a growing collection of helpers I use. It will grow with my
+understanding and needs with this... product.
+
+# Location
+
+If you find this on GitHub - this is a fork from outside GitHub. Its
+upstream is on a non-Microsoft git. (Details may follow - right now
+it's on a public but privately maintained git server, it may be migrated
+to a public FOSS instance like Codeberg or whatnot.) \ No newline at end of file
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 0000000..a94be89
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,41 @@
+#!powershell
+# vim:syntax=ps1:ts=4
+
+# We deploy to the user directory (yep, Documents, cheers MS :D):
+$UserPSPath = "${env:USERPROFILE}\Documents\WindowsPowerShell"
+$ModulePath = "${UserPSPath}\Modules"
+# $InstPath = "${ModulePath}\hpf-cons"
+
+$instMods = @(
+ 'AzureHelpers'
+)
+
+# Where are we? (Ensures we can be called from just anywhere, the 90s want
+# their "cd something && .\whatever" back.)
+$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
+
+# Ensure we stop on any error. We just write simple commands sequentially,
+# so we need this.
+$ErrorActionPreference = 'Stop'
+
+if ( -not (Test-Path "$UserPSPath") ) {
+ New-Item -ItemType Directory -Path "$UserPSPath"
+}
+if ( -not (Test-Path "$ModulePath") ) {
+ New-Item -ItemType Directory -Path "$ModulePath"
+}
+# if ( -not (Test-Path "$InstPath") ) {
+# New-Item -ItemType Directory -Path "$InstPath"
+# }
+
+# We could now use robocopy to ensure timestamps and stuff, but since this will
+# eventually pull from git, this is irrelevant - so we can resort to the more
+# primitive .NET-Cmdlets.
+# To ensure files that don't exist anymore, we'd need robocopy again :-)
+foreach ($instMod in $instMods) {
+ Write-Host "Copying items..."
+ Copy-Item -Recurse -Path "${scriptPath}\${instMod}" -Destination "$ModulePath" -Force
+ Write-Host "...done."
+}
+
+# Done!
diff --git a/moduleHelper.ps1 b/moduleHelper.ps1
new file mode 100644
index 0000000..0bca260
--- /dev/null
+++ b/moduleHelper.ps1
@@ -0,0 +1,225 @@
+#!powershell
+# vim:syntax=ps1:ts=4
+# Do not remove the shebang or vim lines above. It helps software
+# (e.g. Notepad++ or rouge-based, not only vim) identifying the syntax. JUST DON'T.
+
+# This helps a user creating a base module structure.
+
+<#
+ .SYNOPSIS
+
+ Helps a user create a PowerShell module.
+
+ .DESCRIPTION
+
+ This script helps creating a base module structure, creating folders and some
+ base files.
+
+ .EXAMPLE
+
+ PS> .\moduleHelper.ps1 -ModuleName 'FooBar' -ModuleAuthor 'LaFoo Barson' -ModuleDescription "I foo'ed a bar."
+
+ .LINK
+
+ https://ramblingcookiemonster.github.io/Building-A-PowerShell-Module/
+#>
+
+Param (
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="The name of the module to create inside this folder.",
+ Position=0
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $ModuleName,
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="The name of the module author.",
+ Position=1
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $ModuleAuthor,
+ [Parameter(
+ Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="A short description of the module. Max. 140 characters.",
+ Position=2
+ )
+ ]
+ [ValidateLength(1,140)]
+ [string]
+ $ModuleDescription,
+ [Parameter(
+ Mandatory=$false,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="The name of the module to create inside this folder.",
+ Position=3
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $CompanyName,
+ [Parameter(
+ Mandatory=$false,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="The module's version. Validated against being SemVer. Default: 0.1",
+ Position=4
+ )
+ ]
+ [ValidateLength(1,16)]
+ [string]
+ $ModuleVersion = '0.1',
+ [Parameter(
+ Mandatory=$false,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="Link to the licence. Defaults to a Wikipedia link about proprietary software.",
+ Position=5
+ )
+ ]
+ [ValidateLength(1,64)]
+ [string]
+ $LicenseUri = 'https://en.wikipedia.org/wiki/Proprietary_software',
+ [Parameter(
+ Mandatory=$false,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="Use Formats Process file? If unsure, ignore this option. If so, set to true.",
+ Position=6
+ )
+ ]
+ [boolean]
+ $FormatsProcess = $false,
+ [Parameter(
+ Mandatory=$false,
+ ValueFromPipelineByPropertyName=$true,
+ HelpMessage="Minimum PowerShell version this module works on. Validated against being SemVer.",
+ Position=7
+ )
+ ]
+ [ValidateLength(1,16)]
+ [string]
+ $PSVersion = '5.1'
+)
+
+$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
+# Based off https://ramblingcookiemonster.github.io/Building-A-PowerShell-Module/,
+# here comes the actual "let's do this!".
+$ModulePath = "${scriptPath}\$ModuleName"
+# $ModuleName = 'PSStackExchange'
+# $ModuleAuthor = 'RamblingCookieMonster'
+$Description = 'PowerShell module to query the StackExchange API'
+
+# The RegEx to validate version numbers against. We imply semantiv versioning, with
+# or without leading "v".
+[regex]$versRex = '^v?[0-9]{1,}(\.[0-9]{1,}){0,2}$'
+
+# Never accept failures, it will lead to subsequent confusion and errors.
+$ErrorActionPreference = 'Stop'
+
+# Create the module and private function directories
+# Write down the array in order of creation, i.e. DIR comes before DIR\SUBDIR.
+$ModuleDirs = @(
+ "$ModulePath"
+ "${ModulePath}\Private"
+ "${ModulePath}\Public"
+ "${ModulePath}\en-US" # For about_Help files
+ "${ModulePath}\Tests"
+)
+foreach ($ModuleDir in $ModuleDirs) {
+ if ( -not (Test-Path "$ModuleDir") ) {
+ New-Item -ItemType Directory -Path "$ModuleDir" | Out-Null
+ }
+}
+
+#Create the module and related files
+$ModuleFiles = @(
+ "${ModulePath}\${ModuleName}.psd1"
+ "${ModulePath}\${ModuleName}.psm1"
+ "${ModulePath}\en-US\about_${ModuleName}.help.txt"
+ "${ModulePath}\Tests\${ModuleName}.Tests.ps1"
+)
+foreach ($ModuleFile in $ModuleFiles) {
+ if ( -not (Test-Path "$ModuleFile") ) {
+ New-Item -ItemType File -Path $ModuleFile | Out-Null
+ }
+}
+if ($FormatsProcess) {
+ New-Item -ItemType File -Path "${ModulePath}\${ModuleName}.Format.ps1xml"
+}
+
+# Set some defaults
+if ( ($ModuleVersion -eq $null) ) {
+ $ModuleVersion = '0.1'
+}
+if ( ($LicenseUri -eq $null) ) {
+ $LicenseUri = 'https://en.wikipedia.org/wiki/Proprietary_software'
+}
+if ( ($PSVersion -eq $null) ) {
+ # Yeah, PS7 is still beta and has lots more features, but consider the conservative
+ # versions that are rolled on Windows server (alongside Xbox extensions, priorities of this company...)
+ # Better stick to 5 as a default. Anything earlier implies EOL systems, so NOPE.
+ $PSVersion = '5.1'
+}
+
+# Input Validation
+if ( -not ($ModuleVersion -match $versRex) ) {
+ throw [System.ArgumentException]::New("Not a version string: ${ModuleVersion}")
+}
+if ( -not ($PSVersion -match $versRex) ) {
+ throw [System.ArgumentException]::New("Not a version string: ${PSVersion}")
+}
+
+# The module UUID ("GUID")
+# New-Guid produces an object with a single attribute, "Guid", containing the string. Here we go.
+$moduleGuid = (New-Guid).Guid
+
+# Declare the Manifest hash:
+$ManifestHash = @{
+ RootModule = "${ModuleName}.psm1"
+ Author = $ModuleAuthor
+ CompanyName = $CompanyName
+ ModuleVersion = $ModuleVersion
+ Guid = $moduleGuid
+ PowerShellVersion = $PSVersion
+ Description = $ModuleDescription
+ PrivateData = @{
+ PSData = @{
+ LicenseUri = $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 = ''
+ } # End of PSData hashtable
+ } # End of PrivateData hashtable
+}
+if ($FormatsProcess) {
+ $ManifestHash += @{ FormatsToProcess = "${ModuleName}.Format.ps1xml" }
+}
+
+# Create the Module:
+# New-ModuleManifest produced a result that does not comply with the standard and fails restricted language settings.
+# Lel. Since Microsoft never cared about an actual module creator and this just creates ModuleName.psd1, let's do This
+# ourselves, we will not fail :-)
+# New-ModuleManifest @ManifestHash
+# here we go. Only caveat: ofc PS can only output well-known formats, but they HAD to have a different syntax for
+# PowerShell internally.
+if ( (Test-Path -Path "${ModulePath}\${ModuleName}.psd1") ) {
+ Remove-Item -Force -Path "${ModulePath}\${ModuleName}.psd1"
+}
+($ManifestHash | ConvertTo-Json) -replace '^{','@{' -replace ':',' = ' -replace ',','' -replace '=\ +{', '= @{' | Out-File "${ModulePath}\${ModuleName}.psd1"
+# Beware: this will look a bit ugly, but it will work. This is on Windows, we have no sophisticated development environments. If you want that - install Linux ;-)
+
+
+# Test and output the result:
+Write-Host "`nRESULT:"
+Write-Host "======"
+Test-ModuleManifest -Path "${ModulePath}\${ModuleName}.psd1" | Select-Object Name,Path,Description,Guid,ModuleBase,PrivateData,Version,ModuleType,Author,AccessMode,ExportedFormatFiles
+
+# Copy the public/exported functions into the public folder, private functions into private folder \ No newline at end of file