├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── GenerateCodeFromApi.ps1 ├── LICENSE ├── PITCHME.md ├── ProxmoxCLI ├── ProxmoxCLI.psd1 ├── ProxmoxCLI.psm1 ├── Templates │ └── Cmdlets.ps1 ├── private │ ├── Api.ps1 │ ├── bypassCertificatePolicy.ps1 │ └── callAPI.ps1 └── public │ ├── ConnectionManager.ps1 │ ├── Get-Container.ps1 │ ├── Get-VM.ps1 │ ├── Get-VMHost.ps1 │ └── New-VM.ps1 ├── README.md ├── appveyor.yml ├── build.clean.modules.ps1 ├── build.ps1 ├── check.ps1 ├── deploy.PSDeploy.ps1 ├── docs ├── Quick-Start-Installation-and-Example.md ├── about.md ├── acknowledgements.md └── index.md ├── mkdocs.yml ├── module.build.ps1 ├── package.json └── spec ├── module.Steps.ps1 └── module.feature /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: quonic 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Edition of Powershell [e.g. Windows, Core] 29 | - Version [e.g. 1, 2, 3, 4, 5, 5.1, 6, 7] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: quonic 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 8 | 9 | 10 | 11 | 12 | ## Motivation and Context 13 | 14 | 15 | ## How Has This Been Tested? 16 | 17 | 18 | 19 | 20 | ## Screenshots (if appropriate): 21 | 22 | ## Types of changes 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 27 | 28 | ## Checklist: 29 | 30 | 31 | - [ ] My code follows the code style of this project. 32 | - [ ] My change requires a change to the documentation. 33 | - [ ] I have updated the documentation accordingly. 34 | - [ ] I have read the **CONTRIBUTING** document. 35 | - [ ] I have added tests to cover my changes. 36 | - [ ] All new and existing tests passed. 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /output/ 2 | node_modules 3 | /tests/UTSetting.ps1 4 | scratch.ps1 5 | .vscode/spellright.dict 6 | .vs/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | // { 8 | // "name": "PowerShell: Launch Current File", 9 | // "type": "PowerShell", 10 | // "request": "launch", 11 | // "script": "${file}", 12 | // "cwd": "${file}" 13 | // } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.classpath": true, 4 | "**/.project": true, 5 | "**/.settings": true, 6 | "**/.factorypath": true 7 | }, 8 | "spellright.language": [ 9 | "en" 10 | ], 11 | "spellright.documentTypes": [ 12 | "latex", 13 | "plaintext" 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Run tests", 8 | "type": "shell", 9 | "command": "pwsh ./build.ps1 Pester", 10 | "windows": { 11 | "command": ".\\build.ps1 Pester" 12 | }, 13 | "group": { 14 | "kind": "test", 15 | "isDefault": true 16 | }, 17 | "presentation": { 18 | "reveal": "always", 19 | "panel": "new" 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. One pull request per issue or feature, so as to reduce complex merges later. 11 | 2. Ensure any install or build dependencies are removed before the end of the layer when doing a 12 | build. 13 | 3. Update the README.md with details of changes to the interface, this includes new environment 14 | variables, exposed ports, and useful file locations. 15 | 4. Increase the version numbers in any examples files and the README.md to the new version that this 16 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 17 | 5. The owner may merge the Pull Request in once testing has complete both Appvoyer and manually. 18 | If a feature can't be tested, then a new test needs to be implimented with the relitive mock 19 | commands. In order to show that the code works as documented. 20 | 21 | 23 | 24 | ## Code of Conduct 25 | 26 | # Contributor Covenant Code of Conduct 27 | 28 | ## Our Pledge 29 | 30 | We as members, contributors, and leaders pledge to make participation in our 31 | community a harassment-free experience for everyone, regardless of age, body 32 | size, visible or invisible disability, ethnicity, sex characteristics, gender 33 | identity and expression, level of experience, education, socio-economic status, 34 | nationality, personal appearance, race, religion, or sexual identity 35 | and orientation. 36 | 37 | We pledge to act and interact in ways that contribute to an open, welcoming, 38 | diverse, inclusive, and healthy community. 39 | 40 | ## Our Standards 41 | 42 | Examples of behavior that contributes to a positive environment for our 43 | community include: 44 | 45 | * Demonstrating empathy and kindness toward other people 46 | * Being respectful of differing opinions, viewpoints, and experiences 47 | * Giving and gracefully accepting constructive feedback 48 | * Accepting responsibility and apologizing to those affected by our mistakes, 49 | and learning from the experience 50 | * Focusing on what is best not just for us as individuals, but for the 51 | overall community 52 | 53 | Examples of unacceptable behavior include: 54 | 55 | * The use of sexualized language or imagery, and sexual attention or 56 | advances of any kind 57 | * Trolling, insulting or derogatory comments, and personal or political attacks 58 | * Public or private harassment 59 | * Publishing others' private information, such as a physical or email 60 | address, without their explicit permission 61 | * Other conduct which could reasonably be considered inappropriate in a 62 | professional setting 63 | 64 | ## Enforcement Responsibilities 65 | 66 | Community leaders are responsible for clarifying and enforcing our standards of 67 | acceptable behavior and will take appropriate and fair corrective action in 68 | response to any behavior that they deem inappropriate, threatening, offensive, 69 | or harmful. 70 | 71 | Community leaders have the right and responsibility to remove, edit, or reject 72 | comments, commits, code, wiki edits, issues, and other contributions that are 73 | not aligned to this Code of Conduct, and will communicate reasons for moderation 74 | decisions when appropriate. 75 | 76 | ## Scope 77 | 78 | This Code of Conduct applies within all community spaces, and also applies when 79 | an individual is officially representing the community in public spaces. 80 | Examples of representing our community include using an official e-mail address, 81 | posting via an official social media account, or acting as an appointed 82 | representative at an online or offline event. 83 | 84 | ## Enforcement 85 | 86 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 87 | reported to the community leaders responsible for enforcement at 88 | [spyingwind@gmail.com]. 89 | All complaints will be reviewed and investigated promptly and fairly. 90 | 91 | All community leaders are obligated to respect the privacy and security of the 92 | reporter of any incident. 93 | 94 | ## Enforcement Guidelines 95 | 96 | Community leaders will follow these Community Impact Guidelines in determining 97 | the consequences for any action they deem in violation of this Code of Conduct: 98 | 99 | ### 1. Correction 100 | 101 | **Community Impact**: Use of inappropriate language or other behavior deemed 102 | unprofessional or unwelcome in the community. 103 | 104 | **Consequence**: A private, written warning from community leaders, providing 105 | clarity around the nature of the violation and an explanation of why the 106 | behavior was inappropriate. A public apology may be requested. 107 | 108 | ### 2. Warning 109 | 110 | **Community Impact**: A violation through a single incident or series 111 | of actions. 112 | 113 | **Consequence**: A warning with consequences for continued behavior. No 114 | interaction with the people involved, including unsolicited interaction with 115 | those enforcing the Code of Conduct, for a specified period of time. This 116 | includes avoiding interactions in community spaces as well as external channels 117 | like social media. Violating these terms may lead to a temporary or 118 | permanent ban. 119 | 120 | ### 3. Temporary Ban 121 | 122 | **Community Impact**: A serious violation of community standards, including 123 | sustained inappropriate behavior. 124 | 125 | **Consequence**: A temporary ban from any sort of interaction or public 126 | communication with the community for a specified period of time. No public or 127 | private interaction with the people involved, including unsolicited interaction 128 | with those enforcing the Code of Conduct, is allowed during this period. 129 | Violating these terms may lead to a permanent ban. 130 | 131 | ### 4. Permanent Ban 132 | 133 | **Community Impact**: Demonstrating a pattern of violation of community 134 | standards, including sustained inappropriate behavior, harassment of an 135 | individual, or aggression toward or disparagement of classes of individuals. 136 | 137 | **Consequence**: A permanent ban from any sort of public interaction within 138 | the community. 139 | 140 | ## Attribution 141 | 142 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 143 | version 2.0, available at 144 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 145 | 146 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 147 | enforcement ladder](https://github.com/mozilla/diversity). 148 | 149 | [homepage]: https://www.contributor-covenant.org 150 | 151 | For answers to common questions about this code of conduct, see the FAQ at 152 | https://www.contributor-covenant.org/faq. Translations are available at 153 | https://www.contributor-covenant.org/translations. 154 | -------------------------------------------------------------------------------- /GenerateCodeFromApi.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [switch] 4 | $Test 5 | ) 6 | function Get-ApiChild { 7 | [OutputType([PSCustomObject[]])] 8 | param ( 9 | [Parameter(Mandatory = $true)] 10 | [PSCustomObject] 11 | $Child, 12 | $ParantPath 13 | ) 14 | if ($Child.text.Count -gt 1) { 15 | for ($i = 0; $i -lt $Child.text.Count; $i++) { 16 | [PSCustomObject]@{ 17 | Name = $Child.text[$i] 18 | Path = $Child.path[$i] 19 | Children = $Child.children[$i] | Where-Object { $_.path -like "$ParantPath*" } | ForEach-Object { $(Get-ApiChild -Child $_ -ParantPath $Child.path[$i]) } 20 | Methods = if ($Child.info[$i]) { $(Get-ApiInfo -Info $Child.info[$i]) }else { $null } 21 | } 22 | } 23 | } 24 | else { 25 | $Child.text | ForEach-Object { 26 | [PSCustomObject]@{ 27 | Name = $Child.text 28 | Path = $Child.path 29 | Children = if ($Child.children) { $(Get-ApiChild -Child $Child.children -ParantPath $Child.path) }else { $null } 30 | Methods = if ($Child.info) { $(Get-ApiInfo -Info $Child.info) }else { $null } 31 | } 32 | } 33 | } 34 | } 35 | 36 | function Get-ApiProperties { 37 | [OutputType([PSCustomObject[]])] 38 | param ( 39 | [Parameter(Mandatory = $true)] 40 | [PSCustomObject[]] 41 | $Properties 42 | ) 43 | ($Properties | Get-Member -MemberType NoteProperty).Name | ForEach-Object { 44 | $Name = $_ 45 | if ($Name -like "remove" -or $Name -like "address") { 46 | [PSCustomObject]@{ 47 | Name = $Name 48 | Required = if (($Properties | Select-Object -ExpandProperty $_).optional -eq 1) { $false } else { $true } 49 | Type = ($Properties | Select-Object -ExpandProperty $_).type 50 | Description = ($Properties | Select-Object -ExpandProperty $_).description 51 | Format = ($Properties | Select-Object -ExpandProperty $_).format 52 | } 53 | } 54 | else { 55 | [PSCustomObject]@{ 56 | Name = $Name 57 | Required = if ($Properties."$Name".optional -eq 1) { $false } else { $true } 58 | Type = $Properties."$Name".type 59 | Description = $Properties."$Name".description 60 | Format = $Properties."$Name".format 61 | } 62 | } 63 | } 64 | } 65 | 66 | function Get-ApiInfo { 67 | param ( 68 | $Info, 69 | $Path 70 | ) 71 | $Info | ForEach-Object { 72 | if ($_.GET) { 73 | [PSCustomObject]@{ 74 | Name = $_.GET.name 75 | Method = $_.GET.method 76 | Description = $_.GET.description 77 | Parameters = if ($_.GET.parameters.properties) { Get-ApiProperties -Properties $_.GET.parameters.properties }else { $null } 78 | # Permissions = $_.GET.permissions # Probably not needed 79 | Returns = $_.GET.returns 80 | } 81 | } 82 | if ($_.POST) { 83 | [PSCustomObject]@{ 84 | Name = $_.POST.name 85 | Method = $_.POST.method 86 | Description = $_.POST.description 87 | Parameters = if ($_.POST.parameters.properties) { Get-ApiProperties -Properties $_.POST.parameters.properties }else { $null } 88 | # Permissions = $_.POST.permissions # Probably not needed 89 | Returns = $_.POST.returns 90 | } 91 | } 92 | if ($_.PUT) { 93 | [PSCustomObject]@{ 94 | Name = $_.PUT.name 95 | Method = $_.PUT.method 96 | Description = $_.PUT.description 97 | Parameters = if ($_.PUT.parameters.properties) { Get-ApiProperties -Properties $_.PUT.parameters.properties }else { $null } 98 | # Permissions = $_.PUT.permissions # Probably not needed 99 | Returns = $_.PUT.returns 100 | } 101 | } 102 | if ($_.DELETE) { 103 | [PSCustomObject]@{ 104 | Name = $_.DELETE.name 105 | Method = $_.DELETE.method 106 | Description = $_.DELETE.description 107 | Parameters = if ($_.DELETE.parameters.properties) { Get-ApiProperties -Properties $_.DELETE.parameters.properties }else { $null } 108 | # Permissions = $_.DELETE.permissions # Probably not needed 109 | Returns = $_.DELETE.returns 110 | } 111 | } 112 | if ($_.PATCH) { 113 | [PSCustomObject]@{ 114 | Name = $_.PATCH.name 115 | Method = $_.PATCH.method 116 | Description = $_.PATCH.description 117 | Parameters = if ($_.PATCH.parameters.properties) { Get-ApiProperties -Properties $_.PATCH.parameters.properties }else { $null } 118 | # Permissions = $_.PATCH.permissions # Probably not needed 119 | Returns = $_.PATCH.returns 120 | } 121 | } 122 | } 123 | } 124 | 125 | function Build-Api { 126 | param ( 127 | [Parameter(Mandatory)] 128 | $Data 129 | ) 130 | Build-ApiCmdlets -Data $Data -Recursive 131 | } 132 | function Build-ApiCmdlets { 133 | param ( 134 | [Parameter(Mandatory)] 135 | $Data, 136 | [switch] 137 | $Recursive 138 | ) 139 | $Data | ForEach-Object { 140 | $Name = $_.Name 141 | $Path = (($_.Path -split '/') | 142 | ForEach-Object { 143 | if ($_ -and -not [string]::IsNullOrEmpty($_) -and -not [string]::IsNullOrWhiteSpace($_)) { 144 | $curName = (Get-Culture).TextInfo.ToTitleCase($_) -replace '_' -replace '-' -replace '{' -replace '}' 145 | if ($curName -notlike "*id" -and $curName -notlike "id") { 146 | $curName 147 | } 148 | 149 | } 150 | } 151 | ) 152 | 153 | if ($Path[1] -and ($Path[0] -replace ".{1}$") -like $Path[1]) { 154 | $Path[0] = $Path[0] -replace ".{1}$" 155 | } 156 | 157 | $FormattedName = ($Path | Select-Object -Unique) -join '' 158 | $NewPath = $_.Path 159 | 160 | $_.Methods | ForEach-Object { 161 | $TitleCaseName = $((Get-Culture).TextInfo.ToTitleCase($Name) -replace '_' -replace '-' -replace '{' -replace '}') 162 | 163 | $Noun = if ($FormattedName -like "*$TitleCaseName") { 164 | "$($FormattedName)" 165 | } 166 | else { 167 | "$($FormattedName)$($TitleCaseName)" 168 | } 169 | 170 | $Verb = switch ($_.Method) { 171 | "GET" { "Get" ; break } 172 | "POST" { "New" ; break } 173 | "PUT" { "Set" ; break } 174 | "DELETE" { "Remove" ; break } 175 | "PATCH" { "Update"; break } 176 | Default { 177 | if ($_.Method -and [string]::IsNullOrEmpty($_.Method) -and [string]::IsNullOrWhiteSpace($_.Method)) { 178 | Write-Warning -Message "Method: $($_.Method) not implimented" 179 | } 180 | } 181 | } 182 | 183 | if ($Verb -and -not [string]::IsNullOrEmpty($Verb) -and -not [string]::IsNullOrWhiteSpace($Verb) -and "$Verb-$Noun" -notlike "New-AccessTicket") { 184 | 185 | "function $Verb-$Noun `{" 186 | "`t[CmdletBinding()]" 187 | # Create OutputType 188 | if ($_.Returns -and $_.Returns.type) { 189 | switch ($_.Returns.type) { 190 | "null" { } 191 | "array" { "`t[OutputType([PSCustomObject[]])]" } 192 | "string" { "`t[OutputType([string])]" } 193 | "integer" { "`t[OutputType([Int32])]" } 194 | "object" { "`t[OutputType([PSCustomObject])]" } 195 | } 196 | } 197 | "`tparam(" 198 | if ($_.Parameters) { 199 | # Create param block 200 | 201 | # Create parameters 202 | $Params = $_.Parameters | ForEach-Object { 203 | if ( 204 | $_.Name -and 205 | -not [string]::IsNullOrEmpty($_.Name) -and 206 | -not [string]::IsNullOrWhiteSpace($_.Name) -and 207 | $_.Name -notin $("debug", "verbose", "force") # Exlude reserved common parameters 208 | ) { 209 | 210 | if ($_.Required) { 211 | "`t`t[Parameter(Mandatory)]" 212 | } 213 | if ($_.Format -and $_.Format -isnot [String]) { 214 | "`t`t# Format:" 215 | $Format = $_.Format 216 | $Format | Get-Member -MemberType NoteProperty | ForEach-Object { 217 | $Name = $_.Name 218 | $FormatString = "`t`t# " 219 | # $Format.$Name | Select-Object -Property format_description, minimum, maximum, pattern 220 | $FormatString = "$($FormatString) $Name=" 221 | if ($Format.$Name.minimum -and -not $Format.$Name.maximum) { 222 | $FormatString = "$($FormatString)<$($Format.$Name.minimum)-N>" 223 | }elseif (-not $Format.$Name.minimum -and $Format.$Name.maximum) { 224 | $FormatString = "$($FormatString)<0-$($Format.$Name.maximum)>" 225 | } 226 | elseif ($Format.$Name.minimum -and $Format.$Name.maximum) { 227 | $FormatString = "$($FormatString)<$($Format.$Name.minimum)-$($Format.$Name.maximum)>" 228 | } 229 | if ($Format.$Name.pattern) { 230 | $FormatString = "$($FormatString)($($Format.$Name.pattern))" 231 | } 232 | "$FormatString" 233 | } 234 | } 235 | # elseif ($_.Format -and $_.Format -is [String]) { 236 | # "`t`t# Format: $($_.Format)" 237 | # } 238 | if ($($_.Name -replace '-') -match ".+\[n\]") { 239 | $pDesc = $_.Description 240 | if ($_.Type -like "boolean") { 241 | $pType = "switch" 242 | } 243 | else { 244 | $pType = $_.Type 245 | } 246 | 247 | $pName = $_.Name -replace '-' 248 | switch ($pName) { 249 | 'acmedomain[n]' { 250 | 0..10 | ForEach-Object { 251 | "`t`t# $(($pDesc -split "`n")[0])" 252 | "`t`t[$($pType)]" 253 | "`t`t`$$($pName -replace "\[n\]")$_," 254 | } 255 | } 256 | 'hostpci[n]' { 257 | 0..10 | ForEach-Object { 258 | "`t`t# $(($pDesc -split "`n")[0])" 259 | "`t`t[$($pType)]" 260 | "`t`t`$$($pName -replace "\[n\]")$_," 261 | } 262 | } 263 | 'ide[n]' { 264 | 0..3 | ForEach-Object { 265 | "`t`t# $(($pDesc -split "`n")[0])" 266 | "`t`t[$($pType)]" 267 | "`t`t`$$($pName -replace "\[n\]")$_," 268 | } 269 | } 270 | 'ipconfig[n]' { 271 | 0..10 | ForEach-Object { 272 | "`t`t# $(($pDesc -split "`n")[0])" 273 | "`t`t[$($pType)]" 274 | "`t`t`$$($pName -replace "\[n\]")$_," 275 | } 276 | } 277 | 'link[n]' { 278 | 0..7 | ForEach-Object { 279 | "`t`t# $(($pDesc -split "`n")[0])" 280 | "`t`t[$($pType)]" 281 | "`t`t`$$($pName -replace "\[n\]")$_," 282 | } 283 | } 284 | 'mp[n]' { 285 | 0..10 | ForEach-Object { 286 | "`t`t# $(($pDesc -split "`n")[0])" 287 | "`t`t[$($pType)]" 288 | "`t`t`$$($pName -replace "\[n\]")$_," 289 | } 290 | } 291 | 'net[n]' { 292 | 0..10 | ForEach-Object { 293 | "`t`t# $(($pDesc -split "`n")[0])" 294 | "`t`t[$($pType)]" 295 | "`t`t`$$($pName -replace "\[n\]")$_," 296 | } 297 | } 298 | 'numa[n]' { 299 | 0..10 | ForEach-Object { 300 | "`t`t# $(($pDesc -split "`n")[0])" 301 | "`t`t[$($pType)]" 302 | "`t`t`$$($pName -replace "\[n\]")$_," 303 | } 304 | } 305 | 'parallel[n]' { 306 | 0..2 | ForEach-Object { 307 | "`t`t# $(($pDesc -split "`n")[0])" 308 | "`t`t[$($pType)]" 309 | "`t`t`$$($pName -replace "\[n\]")$_," 310 | } 311 | } 312 | 'sata[n]' { 313 | 0..5 | ForEach-Object { 314 | "`t`t# $(($pDesc -split "`n")[0])" 315 | "`t`t[$($pType)]" 316 | "`t`t`$$($pName -replace "\[n\]")$_," 317 | } 318 | } 319 | 'scsi[n]' { 320 | 0..30 | ForEach-Object { 321 | "`t`t# $(($pDesc -split "`n")[0])" 322 | "`t`t[$($pType)]" 323 | "`t`t`$$($pName -replace "\[n\]")$_," 324 | } 325 | } 326 | 'serial[n]' { 327 | 0..3 | ForEach-Object { 328 | "`t`t# $(($pDesc -split "`n")[0])" 329 | "`t`t[$($pType)]" 330 | "`t`t`$$($pName -replace "\[n\]")$_," 331 | } 332 | } 333 | 'unused[n]' {} 334 | 'usb[n]' { 335 | 0..4 | ForEach-Object { 336 | "`t`t# $(($pDesc -split "`n")[0])" 337 | "`t`t[$($pType)]" 338 | "`t`t`$$($pName -replace "\[n\]")$_," 339 | } 340 | } 341 | 'virtio[n]' { 342 | 0..15 | ForEach-Object { 343 | "`t`t# $(($pDesc -split "`n")[0])" 344 | "`t`t[$($pType)]" 345 | "`t`t`$$($pName -replace "\[n\]")$_," 346 | } 347 | } 348 | Default {} 349 | } 350 | } 351 | else { 352 | "`t`t# $(($_.Description -split "`n")[0])" 353 | # "`t`t# $(($_.Format -split "`n")[0])" 354 | if ($_.Type -like "boolean") { 355 | "`t`t[switch]" 356 | } 357 | elseif ($_.Name -like "*password*") { 358 | "`t`t[securestring]" 359 | } 360 | else { 361 | "`t`t[$($_.Type)]" 362 | } 363 | if ($_.Name -like "args") { 364 | "`t`t`$arguments," 365 | } 366 | else { 367 | "`t`t`$$($_.Name -replace '-')," 368 | } 369 | 370 | } 371 | } 372 | } 373 | if ($Params) { 374 | ($Params -join "`n").TrimEnd(',') 375 | # Figure out what params are need in the path/uri 376 | $ParamUri = ($_.Parameters | Where-Object { $Path -like "{$($_.Name)}" }).Name 377 | 378 | $ParamUri | ForEach-Object { 379 | # $PathParams = $null 380 | # $PathParams = $NewPath -split "/" | Where-Object { $_ -like "{*}" } | ForEach-Object { $_ -replace "{$($_)}", "$($_ -replace '_')" } 381 | $NewPath = $NewPath -replace "{$($_)}", "`$$($_ -replace '_')" 382 | } 383 | } 384 | 385 | "`t)" 386 | # Add required to $Options and skip any in the path/uri 387 | $OptionsList = "" 388 | $_.Parameters | Where-Object { $_.Required -and $Path -match "{$($_.Name)}" } | ForEach-Object { 389 | $OptionsList += "`t`$Options.Add('$($_.Name)', `$$($_.Name -replace '-'))" 390 | } 391 | if ($OptionsList) { 392 | # Init $Options array if there are options to use 393 | "`t`$Options = @()" 394 | $OptionsList 395 | } 396 | # Add optional params to $Options 397 | $_.Parameters | Where-Object { -not $_.Required } | ForEach-Object { 398 | if ($($_.Name -replace '-') -match ".+\[n\]") { 399 | $pName = $_.Name -replace '-' 400 | switch ($pName) { 401 | 'acmedomain[n]' { 402 | 0..10 | ForEach-Object { 403 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 404 | } 405 | } 406 | 'hostpci[n]' { 407 | 0..10 | ForEach-Object { 408 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 409 | } 410 | } 411 | 'ide[n]' { 412 | 0..3 | ForEach-Object { 413 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 414 | } 415 | } 416 | 'ipconfig[n]' { 417 | 0..10 | ForEach-Object { 418 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 419 | } 420 | } 421 | 'link[n]' { 422 | 0..7 | ForEach-Object { 423 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 424 | } 425 | } 426 | 'mp[n]' { 427 | 0..10 | ForEach-Object { 428 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 429 | } 430 | } 431 | 'net[n]' { 432 | 0..10 | ForEach-Object { 433 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 434 | } 435 | } 436 | 'numa[n]' { 437 | 0..10 | ForEach-Object { 438 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 439 | } 440 | } 441 | 'parallel[n]' { 442 | 0..2 | ForEach-Object { 443 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 444 | } 445 | } 446 | 'sata[n]' { 447 | 0..5 | ForEach-Object { 448 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 449 | } 450 | } 451 | 'scsi[n]' { 452 | 0..30 | ForEach-Object { 453 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 454 | } 455 | } 456 | 'serial[n]' { 457 | 0..3 | ForEach-Object { 458 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 459 | } 460 | } 461 | 'unused[n]' {} 462 | 'usb[n]' { 463 | 0..4 | ForEach-Object { 464 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 465 | } 466 | } 467 | 'virtio[n]' { 468 | 0..15 | ForEach-Object { 469 | "`tif (`$$($pName -replace "\[n\]")$_ -and -not [String]::IsNullOrEmpty(`$$($pName -replace "\[n\]")$_) -and -not [String]::IsNullOrWhiteSpace(`$$($pName -replace "\[n\]")$_)) { `$Options.Add('$($pName -replace "\[n\]")$_',`$$($pName -replace "\[n\]")$_) }" 470 | } 471 | } 472 | Default {} 473 | } 474 | } 475 | else { 476 | if ($_.Type -like "boolean") { 477 | # Handle booleans 478 | "`tif (`$$($_.Name -replace '-')) { `$Options.Add('$($_.Name)', `$$($_.Name -replace '-')) }" 479 | } 480 | elseif ($_.Name -like "*password*") { 481 | # Handle password parameters 482 | "`tif (`$$($_.Name -replace '-')) { `$Options.Add('$($_.Name)', `$(`$$($_.Name -replace '-') | ConvertFrom-SecureString -AsPlainText)) }" 483 | } 484 | elseif ($_.Name -like "args") { 485 | # Handle args 486 | "`tif (`$arguments -and -not [String]::IsNullOrEmpty(`$arguments) -and -not [String]::IsNullOrWhiteSpace(`$arguments)) { `$Options.Add('$($_.Name)', `$arguments) }" 487 | } 488 | else { 489 | "`tif (`$$($_.Name -replace '-') -and -not [String]::IsNullOrEmpty(`$$($_.Name -replace '-')) -and -not [String]::IsNullOrWhiteSpace(`$$($_.Name -replace '-'))) { `$Options.Add('$($_.Name)', `$$($_.Name -replace '-')) }" 490 | } 491 | } 492 | } 493 | # Invoke API call 494 | if ($OptionsList) { 495 | "`tInvoke-ProxmoxAPI -Method $($_.Method) -Resource `"$($NewPath -replace "{","$" -replace "}")`" -Options `$Options" 496 | } 497 | else { 498 | "`tInvoke-ProxmoxAPI -Method $($_.Method) -Resource `"$($NewPath -replace "{","$" -replace "}")`"" 499 | } 500 | } 501 | else { 502 | "`t)" 503 | "`tInvoke-ProxmoxAPI -Method $($_.Method) -Resource `"$($NewPath -replace "{","$" -replace "}")`"" 504 | } 505 | "}" 506 | } 507 | } 508 | if ($_.Children -and $Recursive) { 509 | Build-ApiCmdlets -Data $_.Children -Recursive 510 | } 511 | } 512 | } 513 | 514 | # Api descrition file 515 | $apidataurl = "https://raw.githubusercontent.com/proxmox/pve-docs/master/api-viewer/apidata.js" 516 | 517 | # Get apidata.js 518 | $data = Invoke-WebRequest -Uri $apidataurl 519 | # Split into an array of strings 520 | $d = $data.Content -split "`n" 521 | # Remove javascript code 522 | $d[0] = $d[0] -replace "const apiSchema \= \[", "[" 523 | # Join everything back together 524 | $json = $d[0..($d.Count - 4)] -join "`r`n" 525 | # Convert from json and Loop through each parent object 526 | $api = $json | ConvertFrom-Json | ForEach-Object { 527 | # Create each child object 528 | Get-ApiChild -Child $_ 529 | } 530 | 531 | $ScriptPath = "./ProxmoxCLI/private/Api.ps1" 532 | # Remove current Api.ps1 533 | Remove-Item -Path $ScriptPath 534 | # Build Api.ps1 from child object 535 | Build-Api -Data $api | Out-File -FilePath $ScriptPath -Force 536 | 537 | # Add module members via Export-ModuleMember 538 | # $functionList = (Select-String -Path $ScriptPath -Pattern "function " -Raw) -replace "function ", "`t'" -replace " \{", "'`n" 539 | # $ExportMemberList = "Export-ModuleMember -Function @(`n" + $($functionList) + "`n)" 540 | # $ExportMemberList | Out-File -FilePath $ScriptPath -Append -Force -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jim Caten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PITCHME.md: -------------------------------------------------------------------------------- 1 | # ProxmoxCLI 2 | 3 | A Proxmox PowerShell module for accessing your Proxmox APIs 4 | 5 | --- 6 | 7 | ## Getting Started 8 | 9 | Install from the PSGallery and Import the module 10 | 11 | Install-Module ProxmoxCLI 12 | Import-Module ProxmoxCLI 13 | 14 | --- 15 | 16 | ### What's next? 17 | 18 | For more information 19 | 20 | * [ProxmoxCLI.readthedocs.io](http://ProxmoxCLI.readthedocs.io) 21 | * [github.com/quonic/ProxmoxCLI](https://github.com/quonic/ProxmoxCLI) 22 | * [quonic.github.io](https://quonic.github.io) 23 | -------------------------------------------------------------------------------- /ProxmoxCLI/ProxmoxCLI.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'ProxmoxCLI' 3 | # 4 | # Generated by: Jim Caten 5 | # 6 | # Generated on: 2017-08-08 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'ProxmoxCLI.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.1.4' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '72205408-4557-4b2e-b182-0cea48eadf15' 22 | 23 | # Author of this module 24 | Author = 'Jim Caten' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Unknown' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2017 Jim Caten. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'A Proxmox module for accessing your Proxmox APIs' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # 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. 72 | # FunctionsToExport = @() 73 | 74 | # 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. 75 | CmdletsToExport = @( 76 | 'Connect-PveServer', 77 | 'Get-PveVersion', 78 | 'Get-Node', 79 | 'Get-Syslog', 80 | 'Invoke-ScanNode', 81 | 'Get-Pool', 82 | 'Start-Guest', 83 | 'Stop-Guest', 84 | 'Suspend-Guest', 85 | 'Shutdown-Guest', 86 | 'Resume-Guest', 87 | 'Reset-Guest', 88 | 'Reboot-Guest', 89 | 'Get-Guest', 90 | 'Clone-Guest', 91 | 'Get-Config', 92 | 'Get-Storage', 93 | 'Get-Realm', 94 | 'Request-Ticket' 95 | ) 96 | 97 | # Variables to export from this module 98 | VariablesToExport = @('PveTickets') 99 | 100 | # 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. 101 | AliasesToExport = @('Verify-Ticket') 102 | 103 | # DSC resources to export from this module 104 | # DscResourcesToExport = @() 105 | 106 | # List of all modules packaged with this module 107 | # ModuleList = @() 108 | 109 | # List of all files packaged with this module 110 | # FileList = @() 111 | 112 | # 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. 113 | PrivateData = @{ 114 | 115 | PSData = @{ 116 | 117 | # Tags applied to this module. These help with module discovery in online galleries. 118 | # Tags = @() 119 | 120 | # A URL to the license for this module. 121 | # LicenseUri = '' 122 | 123 | # A URL to the main website for this project. 124 | # ProjectUri = '' 125 | 126 | # A URL to an icon representing this module. 127 | # IconUri = '' 128 | 129 | # ReleaseNotes of this module 130 | # ReleaseNotes = '' 131 | 132 | } # End of PSData hashtable 133 | 134 | } # End of PrivateData hashtable 135 | 136 | # HelpInfo URI of this module 137 | # HelpInfoURI = '' 138 | 139 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 140 | # DefaultCommandPrefix = '' 141 | 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /ProxmoxCLI/ProxmoxCLI.psm1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param() 3 | Write-Verbose "This psm1 is replaced in the build output. This file is only used for debugging." 4 | Write-Verbose $PSScriptRoot 5 | 6 | Write-Verbose 'Import everything in sub folders' 7 | foreach ($folder in @('classes', 'private', 'public', 'includes', 'internal')) { 8 | $root = Join-Path -Path $PSScriptRoot -ChildPath $folder 9 | if (Test-Path -Path $root) { 10 | Write-Verbose "processing folder $root" 11 | $files = Get-ChildItem -Path $root -Filter *.ps1 -Recurse 12 | 13 | # dot source each file 14 | $files | where-Object { $_.name -NotLike '*.Tests.ps1' } | 15 | ForEach-Object { Write-Verbose $_.basename; . $_.FullName } 16 | } 17 | } 18 | 19 | Export-ModuleMember -function (Get-ChildItem -Path "$PSScriptRoot\public\*.ps1").basename -------------------------------------------------------------------------------- /ProxmoxCLI/Templates/Cmdlets.ps1: -------------------------------------------------------------------------------- 1 | function Update-API { 2 | <# 3 | .SYNOPSIS 4 | Example synosis here 5 | 6 | .DESCRIPTION 7 | Example description here 8 | 9 | .PARAMETER ExampleRequired 10 | Example Required 11 | 12 | .PARAMETER ExamplePattern 13 | Example Pattern 14 | 15 | .PARAMETER ExampleString 16 | Example String 17 | 18 | .PARAMETER ExampleBoolean 19 | Example Boolean 20 | 21 | .PARAMETER ExampleInteger 22 | Example Integer 23 | 24 | .PARAMETER ExampleEnum 25 | Example Enum 26 | 27 | .EXAMPLE 28 | Update-API -ExamplePattern "example text" 29 | 30 | .NOTES 31 | General notes 32 | #> 33 | [Diagnostics.CodeAnalysis.SuppressMessage("PSUseShouldProcessForStateChangingFunctions", Scope = "function")] 34 | [CmdletBinding()] 35 | param ( 36 | [Parameter(Mandatory = $True)] 37 | [string] 38 | $ExampleRequired, 39 | [Parameter(Mandatory = $False)] 40 | [ValidatePattern('\w+=[^,]+(,\s*\w+=[^,]+)*')] 41 | [string] 42 | $ExamplePattern, 43 | [Parameter(Mandatory = $False)] 44 | [string] 45 | $ExampleString, 46 | [Parameter(Mandatory = $False)] 47 | [switch] 48 | $ExampleBoolean, 49 | [Parameter(Mandatory = $False)] 50 | [ValidateRange(1, 65535)] 51 | [int] 52 | $ExampleInteger, 53 | [Parameter(Mandatory = $False)] 54 | [ValidateSet('a', 'b')] 55 | [string] 56 | $ExampleEnum 57 | ) 58 | $Options = @() 59 | # string 60 | if ($ExampleString -and -not [String]::IsNullOrEmpty($ExampleString) -and -not [String]::IsNullOrWhiteSpace($ExampleString)) { $Options.Add('ExampleString', $ExampleString) } 61 | # integer 62 | if ($ExampleInteger -and -not [String]::IsNullOrEmpty($ExampleInteger) -and -not [String]::IsNullOrWhiteSpace($ExampleInteger)) { $Options.Add('ExampleInteger', $ExampleInteger) } 63 | # enum 64 | if ($ExampleEnum -and -not [String]::IsNullOrEmpty($ExampleEnum) -and -not [String]::IsNullOrWhiteSpace($ExampleEnum)) { $Options.Add('ExampleEnum', $ExampleEnum) } 65 | # regex string 66 | if ($ExamplePattern -and -not [String]::IsNullOrEmpty($ExamplePattern) -and -not [String]::IsNullOrWhiteSpace($ExamplePattern)) { $Options.Add('ExamplePattern', $ExamplePattern) } 67 | # boolean 68 | if ($ExampleBoolean) { $Options.Add('example-boolean', $ExampleBoolean) } 69 | Invoke-ProxmoxAPI -Method Put -Resource "example/folder/$ExampleRequired" -Options $Options 70 | } -------------------------------------------------------------------------------- /ProxmoxCLI/private/bypassCertificatePolicy.ps1: -------------------------------------------------------------------------------- 1 | function GetTrustAllCertsPolicy () { 2 | # Trust all certs as we don't use an internal CA 3 | # Remove this if you do use an internal CA or are using an external CA 4 | add-type @" 5 | using System.Net; 6 | using System.Security.Cryptography.X509Certificates; 7 | public class TrustAllCertsPolicy : ICertificatePolicy { 8 | public bool CheckValidationResult( 9 | ServicePoint srvPoint, X509Certificate certificate, 10 | WebRequest request, int certificateProblem) { 11 | return true; 12 | } 13 | } 14 | "@ 15 | 16 | return $(New-Object TrustAllCertsPolicy) 17 | } 18 | 19 | function SetCertificatePolicy ($Func) { 20 | [System.Net.ServicePointManager]::CertificatePolicy = $Func 21 | } 22 | 23 | function GetCertificatePolicy () { 24 | return [System.Net.ServicePointManager]::CertificatePolicy 25 | } -------------------------------------------------------------------------------- /ProxmoxCLI/private/callAPI.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-ProxmoxAPI { 2 | #[CmdletBinding()] 3 | Param( 4 | [Parameter(Position = 0, Mandatory)] 5 | [string] 6 | $Resource, 7 | [string] 8 | $Method, 9 | [hashtable] 10 | $Options 11 | ) 12 | if ($null -eq $Script:PveTickets) { 13 | # Check if we even have a ticket 14 | Write-Error "Please connect usinge Connect-PveServer." 15 | return $false 16 | } 17 | # if ((Get-Date).Ticks -le $Script:PveTickets.Expire) { 18 | # # Check if ticket expired and grab a new one 19 | # Connect-PveServer -Server $Script:PveTickets.Server 20 | # } 21 | # Bypass ssl checking or servers without a public cert or internal CA cert 22 | if ($Script:PveTickets.BypassSSLCheck) { 23 | $CertificatePolicy = GetCertificatePolicy 24 | SetCertificatePolicy -Func (GetTrustAllCertsPolicy) 25 | } 26 | 27 | # Setup Headers and cookie for splatting 28 | switch ($Method) { 29 | Get { $splat = PrepareGetRequest; break } 30 | Post { $splat = PreparePostRequest; break } 31 | Put { $splat = PreparePutRequest; break } 32 | Delete { $splat = PrepareGetRequest; break } 33 | Default { $splat = PrepareGetRequest } 34 | } 35 | 36 | $Query = "" 37 | if ($Options) { 38 | $Query = "?" 39 | $Options.keys | ForEach-Object { 40 | if ($Options[$_]) { 41 | $Query = $Query + "$_=$($Options[$_])&" 42 | } 43 | } 44 | $Query = $Query.TrimEnd("&") 45 | } 46 | try { 47 | Write-Debug "REST call: https://$($Script:PveTickets.Server):8006/api2/json/$($Resource)$($Query)" 48 | $response = Invoke-RestMethod -Uri "https://$($Script:PveTickets.Server):8006/api2/json/$($Resource)$($Query)" @splat 49 | Write-Debug "REST response: $($response.data)" 50 | } 51 | catch { return $false } 52 | 53 | 54 | if ($Script:PveTickets.BypassSSLCheck) { 55 | # restore original cert policy 56 | SetCertificatePolicy -Func $CertificatePolicy 57 | } 58 | 59 | return $response.data 60 | } 61 | 62 | function PreparePostRequest() { 63 | $cookie = New-Object System.Net.Cookie -Property @{ 64 | Name = "PVEAuthCookie" 65 | Path = "/" 66 | Domain = $Script:PveTickets.Server 67 | Value = $Script:PveTickets.Ticket 68 | } 69 | $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession 70 | $session.cookies.add($cookie) 71 | $request = New-Object -TypeName PSCustomObject -Property @{ 72 | Method = "Post" 73 | Headers = @{CSRFPreventionToken = $Script:PveTickets.CSRFPreventionToken } 74 | WebSession = $session 75 | ContentType = "application/json" 76 | } 77 | return $request 78 | } 79 | 80 | function PreparePutRequest() { 81 | $cookie = New-Object System.Net.Cookie -Property @{ 82 | Name = "PVEAuthCookie" 83 | Path = "/" 84 | Domain = $Script:PveTickets.Server 85 | Value = $Script:PveTickets.Ticket 86 | } 87 | $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession 88 | $session.cookies.add($cookie) 89 | $request = New-Object -TypeName PSCustomObject -Property @{ 90 | Method = "Put" 91 | Headers = @{CSRFPreventionToken = $Script:PveTickets.CSRFPreventionToken } 92 | WebSession = $session 93 | ContentType = "application/json" 94 | } 95 | return $request 96 | } 97 | 98 | function PrepareGetRequest() { 99 | $cookie = New-Object System.Net.Cookie -Property @{ 100 | Name = "PVEAuthCookie" 101 | Path = "/" 102 | Domain = $Script:PveTickets.Server 103 | Value = $Script:PveTickets.Ticket 104 | } 105 | $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession 106 | $session.cookies.add($cookie) 107 | $request = @{ 108 | Method = "Get" 109 | WebSession = $session 110 | ContentType = "application/json" 111 | } 112 | return $request 113 | } 114 | 115 | function PrepareDeleteRequest() { 116 | $cookie = New-Object System.Net.Cookie -Property @{ 117 | Name = "PVEAuthCookie" 118 | Path = "/" 119 | Domain = $Script:PveTickets.Server 120 | Value = $Script:PveTickets.Ticket 121 | } 122 | $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession 123 | $session.cookies.add($cookie) 124 | $request = New-Object -TypeName PSCustomObject -Property @{ 125 | Method = "Delete" 126 | Headers = @{CSRFPreventionToken = $Script:PveTickets.CSRFPreventionToken } 127 | WebSession = $session 128 | ContentType = "application/json" 129 | } 130 | return $request 131 | } -------------------------------------------------------------------------------- /ProxmoxCLI/public/ConnectionManager.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Connect to Proxmox server 4 | 5 | .DESCRIPTION 6 | Connect to Proxmox server 7 | 8 | .PARAMETER Server 9 | Server name or IP address 10 | 11 | .PARAMETER Credentials 12 | Username and password, username formated as user@pam, user@pve, or user@yourdomain 13 | 14 | .PARAMETER BypassSSLCheck 15 | Used for bypassing Invoke-RestMethod built in SSL checker 16 | 17 | .EXAMPLE 18 | Connect-PveServer -Server "192.168.128.115" -Credentials (Get-Credential -Username "root@pam") -BypassSSLCheck 19 | 20 | .NOTES 21 | This must be used before any other cmdlets are used 22 | #> 23 | function Connect-PveServer { 24 | [CmdletBinding()] 25 | param ( 26 | [Alias("Host", "PveServer")] 27 | [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 28 | [String] 29 | $Server, 30 | [Parameter(Position = 1)] 31 | [pscredential] 32 | $Credentials, 33 | [Switch] 34 | $BypassSSLCheck 35 | ) 36 | 37 | begin { 38 | # Check if ticket already exists and if it is expired 39 | if ($null -eq $Script:PveTickets) { 40 | if (-not ($Credentials)) { 41 | $Credential = Get-Credential -Message "Proxmox Username and password, user@pam, user@pve, or user@domain" 42 | } 43 | 44 | $UserName = $Credential.UserName 45 | $Password = $Credential.GetNetworkCredential().Password 46 | $Body = @{ 47 | username = $UserName 48 | password = $Password 49 | } 50 | } 51 | elseif ((Get-Date).Ticks -ge $Script:PveTickets.Expire) { 52 | # Check if ticket expired and assign the username and ticket 53 | $Body = @{ 54 | username = $Script:PveTickets.UserName 55 | password = $Script:PveTickets.Ticket 56 | } 57 | } 58 | else { 59 | Write-Verbose "Ticket not expired" 60 | Write-Warning "Connected to server $($Script:PveTickets.Server)" 61 | } 62 | # if (-not $Script:PveTickets) { 63 | # $Script:PveTickets = New-Object -TypeName PSCustomObject 64 | # } 65 | if ($BypassSSLCheck -or $Script:PveTickets.BypassSSLCheck) { 66 | # Trust all certs as we don't use an internal CA 67 | # Don't use this if you do use an internal CA or are using an external CA 68 | #$Script:PveTickets.BypassSSLCheck = $true 69 | $CertificatePolicy = GetCertificatePolicy 70 | SetCertificatePolicy -Func (GetTrustAllCertsPolicy) 71 | } 72 | } 73 | 74 | process { 75 | $Url = "https://$($Server):8006/api2/json/access/ticket" 76 | $response = Invoke-RestMethod -Method Post -Uri $Url -Body $Body 77 | if ($response) { 78 | # Create variable to work with as we have a ticket for future auth 79 | $NewServer = @{ } 80 | $NewServer.Server = $Server 81 | $NewServer.UserName = $UserName 82 | $NewServer.Ticket = $response.data.ticket 83 | $NewServer.CSRFPreventionToken = $response.data.CSRFPreventionToken 84 | $NewServer.Expire = (Get-Date).AddHours(2).Ticks 85 | $NewServer.BypassSSLCheck = $false 86 | if ($BypassSSLCheck) { 87 | $NewServer.BypassSSLCheck = $true 88 | } 89 | if ($Script:PveTickets.Server -contains $Server) { 90 | $Script:PveTickets = $Script:PveTickets | ForEach-Object { 91 | if ($_.Server -notlike $Server) { $_ } 92 | } 93 | } 94 | $Script:PveTickets += New-Object PSObject -Property $NewServer 95 | } 96 | else { 97 | Write-Warning "Not able to connect to server: $Server\n Response: $response" 98 | } 99 | } 100 | 101 | end { 102 | if ($BypassSSLCheck -or $Script:PveTickets.BypassSSLCheck) { 103 | SetCertificatePolicy -Func ($CertificatePolicy) 104 | } 105 | } 106 | } 107 | 108 | Export-ModuleMember -Function @( 109 | 'Connect-PveServer' 110 | ) -Variable @( 111 | 'PveTickets' 112 | ) -------------------------------------------------------------------------------- /ProxmoxCLI/public/Get-Container.ps1: -------------------------------------------------------------------------------- 1 | function Get-Container { 2 | [CmdletBinding(DefaultParameterSetName = "Default")] 3 | [OutputType([PSObject[]])] 4 | param( 5 | # Id of the LXC guest 6 | [Parameter(Mandatory = $false, ParameterSetName = "Id", ValueFromPipelineByPropertyName)] 7 | [string[]] 8 | $Id, 9 | # Node(s) that the LXC guest(s) are running under 10 | [Parameter(Mandatory = $false, ParameterSetName = "Id", ValueFromPipelineByPropertyName)] 11 | [int[]] 12 | $Node 13 | ) 14 | 15 | begin { 16 | } 17 | 18 | process { 19 | if ($Node) { 20 | $Node | ForEach-Object { 21 | if ($Id) { 22 | $N = $_ 23 | $Id | ForEach-Object { 24 | Get-NodeLxcVmid -node $N -vmid $_ 25 | } 26 | 27 | } 28 | else { 29 | Get-NodeLxc -node $_ 30 | } 31 | } 32 | } 33 | else { 34 | Get-Node | ForEach-Object { 35 | Get-NodeLxc -node $_.node 36 | } 37 | } 38 | } 39 | 40 | end { 41 | } 42 | } -------------------------------------------------------------------------------- /ProxmoxCLI/public/Get-VM.ps1: -------------------------------------------------------------------------------- 1 | function Get-VM { 2 | [CmdletBinding(DefaultParameterSetName = "Default")] 3 | [OutputType([PSObject[]])] 4 | param( 5 | # Id of the Qemu guest 6 | [Parameter(Mandatory = $false, ParameterSetName = "Id", ValueFromPipelineByPropertyName)] 7 | [string[]] 8 | $Id, 9 | # Node(s) that the Qemu guest(s) are running under 10 | [Parameter(Mandatory = $false, ParameterSetName = "Id", ValueFromPipelineByPropertyName)] 11 | [int[]] 12 | $Node 13 | ) 14 | 15 | begin { 16 | } 17 | 18 | process { 19 | if ($Node) { 20 | $Node | ForEach-Object { 21 | if ($Id) { 22 | Get-NodeQemu -node $_ | Where-Object { $_ -in $Id } 23 | } 24 | else { 25 | Get-NodeQemu -node $_ 26 | } 27 | } 28 | } 29 | else { 30 | Get-Node | ForEach-Object { 31 | Get-NodeQemu -node $_.node 32 | } 33 | } 34 | } 35 | 36 | end { 37 | } 38 | } -------------------------------------------------------------------------------- /ProxmoxCLI/public/Get-VMHost.ps1: -------------------------------------------------------------------------------- 1 | function Get-VMHost { 2 | [CmdletBinding(DefaultParameterSetName = "Default")] 3 | [OutputType([PSObject[]])] 4 | param( 5 | # Name(s) of the host in the cluster 6 | [Parameter(Mandatory = $false, ParameterSetName = "Name", ValueFromPipelineByPropertyName)] 7 | [string[]] 8 | $Name 9 | ) 10 | 11 | begin { 12 | } 13 | 14 | process { 15 | if ($Name) { 16 | $Name | ForEach-Object { 17 | $N = $_ 18 | Get-Nodes | Where-Object { $_.Name -like $N } 19 | } 20 | } 21 | else { 22 | Get-Nodes 23 | } 24 | } 25 | 26 | end { 27 | } 28 | } -------------------------------------------------------------------------------- /ProxmoxCLI/public/New-VM.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Initally supporting and not supporting: 3 | 4 | Create disks automaticly? Maybe 5 | Not support paralel[n] 6 | Not support serial[n] 7 | Not support smbios1 8 | Not support spice_enhancements 9 | Not support startup 10 | Not support template 11 | Not support tpmstate0 12 | Not support vga 13 | Not support vmgenid, is autogenerated 14 | Not support vmstatestorage 15 | Not support watchdog, is do then it needs to be a param set with $Agent 16 | 17 | 18 | TODO: 19 | Create Kvm, switch, default $true 20 | Create Machine, string, [ValidatePattern("(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)")] 21 | Create Name, string 22 | Create net[n] as param set? 23 | Create ipconfig as param set 24 | - Create Unique, switch, for mac address 25 | Create Numa as param set, default 0 26 | Create NoStartOnBoot, switch? 27 | Create OsType, string, [ValidateSet("other","wxp","w2k","w2k3","w2k8","wvista","win7","win8","win10","win11","l24","l26","solaris")] 28 | Create Pool, string, Validate pool name? 29 | Create Protect, switch 30 | Create Shares, int, [ValidateRange(0,50000)] 31 | Create DisableSMP, switch 32 | Create DisableSockets, switch 33 | Create Start, switch 34 | Create DefaultStorage, string 35 | Create DisableTablet, switch 36 | Create Tags, string[] 37 | Create Usb as param set, PSCustomObject[] 38 | - HostDevice, string, validate how? if at all. 39 | - Usb3, switch/bool 40 | Create VCPU, int, range 1,1028 41 | 42 | #> 43 | 44 | function New-VM { 45 | [CmdletBinding(DefaultParameterSetName = "Default")] 46 | [OutputType([PSObject[]])] 47 | param( 48 | [Parameter(Mandatory = $true)] 49 | [string] 50 | $Node, 51 | [Parameter(Mandatory = $true)] 52 | [string] 53 | $Id, 54 | [switch] 55 | $Agent, 56 | [hashtable] 57 | $QemuArgument, 58 | [switch] 59 | $AutoStart, 60 | [int] 61 | $Memory, 62 | [ValidateScript({ $_ -le $Memory })] 63 | [int] 64 | $Balloon, 65 | # Disk name(s), ie ide0, sata0, sata1, scsi0 66 | [string[]] 67 | $BootOrder, 68 | [ValidateSet("seabios", "ovmf")] 69 | [string] 70 | $Bios = "seabios", 71 | # Volume path to iso or physical disk, this is an alias for option ide2 72 | [string] 73 | $Cdrom, 74 | [Parameter(Mandatory, ParameterSetName = "CloudInit")] 75 | [ValidateSet("nocloud", "configdrive2", "opennebula")] 76 | [string] 77 | $CloudInit, 78 | # Creates a user name 79 | [Parameter(Mandatory, ParameterSetName = "CloudInit")] 80 | [string] 81 | $User, 82 | # OpenSSH formatted key. One key is support at this time. 83 | [Parameter(Mandatory, ParameterSetName = "CloudInit")] 84 | [string] 85 | $SshKey, 86 | [Parameter(ParameterSetName = "CloudInit")] 87 | [string] 88 | $NameServer 89 | ) 90 | 91 | begin { 92 | } 93 | 94 | process { 95 | if ($QemuArgument) { 96 | $Arguments = $QemuArgument | ForEach-Object { "-$($_.Key)$(if($_.Value){"=$_.Value"})" } -join ' ' 97 | } 98 | $Splat = @{ 99 | node = $Node 100 | vmid = $Id 101 | agent = "enable=$(if ($Agent) { 1 }else { 0 })" 102 | arguments = $Arguments 103 | autostart = $AutoStart 104 | } 105 | New-NodeQemu @Splat 106 | } 107 | 108 | end { 109 | } 110 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProxmoxCLI 2 | 3 | A Proxmox module for accessing your Proxmox APIs similar in functionality to PowerCLI for VMWare 4 | 5 | ## Current Stage 6 | 7 | * Developing and Testing 8 | * NOT Production Ready, yet. 9 | * Work on a way to dynamicly generating cmdlets from [apidata.js](https://raw.githubusercontent.com/proxmox/pve-docs/master/api-viewer/apidata.js) 10 | 11 | [![Build status](https://ci.appveyor.com/api/projects/status/pxsta8uglrc9kql8?svg=true)](https://ci.appveyor.com/project/quonic/proxmoxcli) 12 | 13 | ## GitPitch PitchMe presentation 14 | 15 | * [gitpitch.com/quonic/ProxmoxCLI](https://gitpitch.com/quonic/ProxmoxCLI) 16 | 17 | ## Contributing 18 | 19 | See [CONTRIBUTING.md](/CONTRIBUTING.md). 20 | 21 | 27 | 28 | ### Building 29 | 30 | * Clone this repository. 31 | * `.\build.ps1` 32 | * `Import-Module .\output\ProxmoxCLI\` 33 | 34 | ### Using ProxmoxCLI 35 | 36 | * Connect to a Proxmox sever with `Connect-PveServer -Server "Proxmox1"`, use `-BypassSSLCheck` if your computer doesn't trust the SSL cert from the Proxmox server. 37 | * Run `Get-Node | Get-Guest` and you should see a list of your guests. 38 | 39 | ## More Information 40 | 41 | For more information 42 | 43 | 44 | * [github.com/quonic/ProxmoxCLI](https://github.com/quonic/ProxmoxCLI) 45 | 46 | 47 | This project was generated using [Kevin Marquette](http://kevinmarquette.github.io)'s [Full Module Plaster Template](https://github.com/KevinMarquette/PlasterTemplates/tree/master/FullModuleTemplate). 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # See http://www.appveyor.com/docs/appveyor-yml for many more options 2 | 3 | 4 | image: 5 | - Visual Studio 2013 # Windows - PowerShell 5.1 6 | - Visual Studio 2022 # Windows - PowerShell 7 7 | - Ubuntu # Linux - PowerShell 7 8 | 9 | # Skip on updates to the readme, doc, package in the commit message. 10 | skip_commits: 11 | message: /updated (package|readme|doc).*|update (package|readme|doc).*s/ 12 | 13 | #Kick off the CI/CD pipeline 14 | test_script: 15 | - ps: . .\build.ps1 -Task Default 16 | - pwsh: . .\build.ps1 -Task Default 17 | build: false -------------------------------------------------------------------------------- /build.clean.modules.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "this will report all modules with duplicate (older and newer) versions installed" 2 | Write-Host "be sure to run this as an admin" -foregroundcolor yellow 3 | Write-Host "(You can update all your Azure RMmodules with update-module Azurerm -force)" 4 | 5 | $mods = Get-InstalledModule 6 | 7 | foreach ($Mod in $mods) { 8 | Write-Host "Checking $($mod.name)" 9 | $latest = Get-InstalledModule $mod.name 10 | $specificmods = Get-InstalledModule $mod.name -allversions 11 | Write-Host "$($specificmods.count) versions of this module found [ $($mod.name) ]" 12 | 13 | if ($specificmods.Count -gt 1) { 14 | foreach ($sm in $specificmods) { 15 | if ($sm.version -eq $latest.version) { 16 | $color = "green" 17 | Write-Host " $($sm.name) - $($sm.version) [highest installed is $($latest.version)]" -foregroundcolor $color 18 | } 19 | else { 20 | $color = "magenta" 21 | Write-Host " $($sm.name) - $($sm.version) [highest installed is $($latest.version)] - Removing..." -foregroundcolor $color 22 | try { 23 | Uninstall-Module -InputObject $sm -Force 24 | } 25 | catch { 26 | $error[0] | Format-List 27 | Write-Host " $($sm.name) - $($sm.version) [highest installed is $($latest.version)] - Skipping" -foregroundcolor $color 28 | } 29 | Write-Host " $($sm.name) - $($sm.version) [highest installed is $($latest.version)] - Removed" -foregroundcolor $color 30 | } 31 | } 32 | Write-Host "------------------------" 33 | } 34 | } 35 | Write-Host "done" -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/powershell -Command 2 | <# 3 | .Description 4 | Installs and loads all the required modules for the build. 5 | Derived from scripts written by Warren F. (RamblingCookieMonster) 6 | #> 7 | 8 | [cmdletbinding()] 9 | param ( 10 | [ValidateSet('Default', 'Build', 'Pester', 'Clean')] 11 | $Task = 'Default' 12 | ) 13 | Write-Output "Starting build" 14 | 15 | # Cleans out old versions of modules from CurrentUser, run as admin to remove System installed Modules 16 | if ( 17 | $null -eq $ENV:BHBuildSystem -or 18 | $null -eq $ENV:BHBranchName -or 19 | $null -eq $ENV:BHCommitMessage 20 | ) { 21 | Write-Output " Remove Duplicate and Old Dependent Modules" 22 | .\build.clean.modules.ps1 23 | } 24 | 25 | # Grab nuget bits, install modules, set build variables, start build. 26 | Write-Output " Install Dependent Modules" 27 | Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null 28 | Install-Module InvokeBuild, PSDeploy, BuildHelpers, PSScriptAnalyzer, PowerShellForGitHub -force -Scope CurrentUser 29 | Install-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser 30 | 31 | Write-Output " Import Dependent Modules" 32 | Import-Module InvokeBuild, BuildHelpers, PSScriptAnalyzer, PowerShellForGitHub 33 | 34 | Set-BuildEnvironment 35 | 36 | Write-Output " InvokeBuild" 37 | Invoke-Build $Task -Result result 38 | if ($Result.Error) { 39 | Get-ChildItem -Path .\ProxmoxCLI\ -Filter "*.ps1" -Recurse | Invoke-ScriptAnalyzer 40 | exit 1 41 | } 42 | else { 43 | exit 0 44 | } -------------------------------------------------------------------------------- /check.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Path .\ProxmoxCLI\ -Filter "*.ps1" -Recurse | Invoke-ScriptAnalyzer -------------------------------------------------------------------------------- /deploy.PSDeploy.ps1: -------------------------------------------------------------------------------- 1 | # Generic module deployment. 2 | # This stuff should be moved to psake for a cleaner deployment view 3 | 4 | # ASSUMPTIONS: 5 | 6 | # folder structure of: 7 | # - RepoFolder 8 | # - This PSDeploy file 9 | # - ModuleName 10 | # - ModuleName.psd1 11 | 12 | # Nuget key in $ENV:NugetApiKey 13 | 14 | # Set-BuildEnvironment from BuildHelpers module has populated ENV:BHProjectName 15 | 16 | # find a folder that has psd1 of same name... 17 | 18 | if ($ENV:BHProjectName -and $ENV:BHProjectName.Count -eq 1) 19 | { 20 | Deploy Module { 21 | By PSGalleryModule { 22 | FromSource output\$ENV:BHProjectName 23 | To PSGallery 24 | WithOptions @{ 25 | ApiKey = $ENV:NugetApiKey 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/Quick-Start-Installation-and-Example.md: -------------------------------------------------------------------------------- 1 | # Installing ProxmoxCLI 2 | 3 | 8 | 9 | ## Building 10 | 11 | * Clone this repository. 12 | * `.\build.ps1` 13 | * `Import-Module .\output\ProxmoxCLI\` 14 | 15 | ## Using ProxmoxCLI 16 | 17 | * Connect to a Proxmox sever with `Connect-PveServer -Server "Proxmox1"`, use `-BypassSSLCheck` if your computer doesn't trust the SSL cert from the Proxmox server. 18 | * Run `Get-Node | Get-Guest` and you should see a list of your guests. 19 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | # What is ProxmoxCLI 2 | 3 | A Proxmox module for accessing your Proxmox APIs 4 | 5 | Authored by Jim Caten 6 | -------------------------------------------------------------------------------- /docs/acknowledgements.md: -------------------------------------------------------------------------------- 1 | # Acknowledgments 2 | 3 | I want to thank everyone that has submitted feedback, ideas and contributions to this project. Some individuals deserve as special thank you for helping make this project a success. 4 | 5 | ## Jim Caten 6 | 7 | The original author of this project is Jim Caten. All of this was made possible by the effort Jim Caten put into getting this project off the ground and the many hours that were spent making everything work. 8 | 9 | ## Kevin Marquette 10 | 11 | This project was generated using a [Plaster template](https://github.com/KevinMarquette/PlasterTemplates) that he put together and shared with the community. It is modeled after the way he build his projects. A lot of the base structure for this project was put in place by that Plaster template. [kevinmarquette.github.io](http://kevinmarquette.github.io) 12 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ProxmoxCLI Docs 2 | 3 | ProxmoxCLI uses ReadTheDocs to host our documentation. This allows us to keep our docs in the repository, without the various limitations that come with the built in GitHub repo wiki. 4 | 5 | If you would like to contribute to the project or these docs, place make a pull request to the project on GitHub. 6 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ProxmoxCLI 2 | repo_url: https://github.com/quonic/ProxmoxCLI 3 | theme: readthedocs 4 | nav: 5 | - Home: index.md 6 | - ProxmoxCLI Basics: 7 | - What is ProxmoxCLI: about.md 8 | - Quick Start: Quick-Start-Installation-and-Example.md 9 | - Acknowledgements: acknowledgements.md 10 | -------------------------------------------------------------------------------- /module.build.ps1: -------------------------------------------------------------------------------- 1 | $script:ModuleName = 'ProxmoxCLI' 2 | 3 | $script:Source = Join-Path $BuildRoot $ModuleName 4 | $script:Output = Join-Path $BuildRoot output 5 | $script:Destination = Join-Path $Output $ModuleName 6 | $script:ModulePath = "$Destination\$ModuleName.psm1" 7 | $script:ManifestPath = "$Destination\$ModuleName.psd1" 8 | $script:Imports = ( 'private', 'public' ) 9 | $script:TestFile = "$PSScriptRoot\output\TestResults_PS$PSVersion`_$TimeStamp.xml" 10 | 11 | Task "Default" Build, Pester, UpdateSource, Publish 12 | Task "Build" CopyToOutput, BuildPSM1, BuildPSD1 13 | Task "Pester" Build, ImportModule, UnitTests, FullTests 14 | 15 | Task Clean { 16 | $null = Remove-Item $Output -Recurse -ErrorAction Ignore 17 | $null = New-Item -Type Directory -Path $Destination 18 | } 19 | 20 | Task UnitTests { 21 | $TestResults = Invoke-Pester -Path tests\*unit* -PassThru -Tag Build -ExcludeTag Slow 22 | if ($TestResults.FailedCount -gt 0) { 23 | Write-Error "Failed [$($TestResults.FailedCount)] Pester tests" 24 | Invoke-ScriptAnalyzer -Path "$Source\*\*.ps1" -IncludeRule $(Get-ScriptAnalyzerRule) 25 | } 26 | } 27 | 28 | Task FullTests { 29 | $TestResults = Invoke-Pester -Path tests -PassThru -OutputFormat NUnitXml -OutputFile $testFile -Tag Build 30 | if ($TestResults.FailedCount -gt 0) { 31 | Write-Error "Failed [$($TestResults.FailedCount)] Pester tests" 32 | Invoke-ScriptAnalyzer -Path "$Source\*\*.ps1" -IncludeRule $(Get-ScriptAnalyzerRule) 33 | } 34 | } 35 | 36 | Task Specification { 37 | $TestResults = Invoke-Gherkin $PSScriptRoot\Spec -PassThru 38 | if ($TestResults.FailedCount -gt 0) { 39 | Write-Error "[$($TestResults.FailedCount)] specification are incomplete" 40 | } 41 | } 42 | 43 | Task CopyToOutput { 44 | 45 | Write-Output " Create Directory [$Destination]" 46 | $null = New-Item -Type Directory -Path $Destination -ErrorAction Ignore 47 | 48 | Get-ChildItem $source -File | 49 | Where-Object name -NotMatch "$ModuleName\.ps[dm]1" | 50 | Copy-Item -Destination $Destination -Force -PassThru | 51 | ForEach-Object { " Create [.{0}]" -f $_.fullname.replace($PSScriptRoot, '') } 52 | 53 | Get-ChildItem $source -Directory | 54 | Where-Object name -NotIn $imports | 55 | Copy-Item -Destination $Destination -Recurse -Force -PassThru | 56 | ForEach-Object { " Create [.{0}]" -f $_.fullname.replace($PSScriptRoot, '') } 57 | } 58 | 59 | Task BuildPSM1 -Inputs (Get-Item "$source\*\*.ps1") -Outputs $ModulePath { 60 | 61 | [System.Text.StringBuilder]$stringbuilder = [System.Text.StringBuilder]::new() 62 | foreach ($folder in $imports ) { 63 | [void]$stringbuilder.AppendLine( "Write-Verbose 'Importing from [$Source\$folder]'" ) 64 | if (Test-Path "$source\$folder") { 65 | $fileList = Get-ChildItem "$source\$folder\*.ps1" | Where-Object Name -NotLike '*.Tests.ps1' 66 | foreach ($file in $fileList) { 67 | $shortName = $file.fullname.replace($PSScriptRoot, '') 68 | Write-Output " Importing [.$shortName]" 69 | [void]$stringbuilder.AppendLine( "# .$shortName" ) 70 | [void]$stringbuilder.AppendLine( [System.IO.File]::ReadAllText($file.fullname) ) 71 | } 72 | } 73 | } 74 | Write-Output " Creating module [$ModulePath]" 75 | Set-Content -Path $ModulePath -Value $stringbuilder.ToString() 76 | } 77 | 78 | Task NextPSGalleryVersion -if (-Not ( Test-Path "$output\version.xml" ) ) -Before BuildPSD1 { 79 | $galleryVersion = Get-NextPSGalleryVersion -Name $ModuleName 80 | $galleryVersion | Export-Clixml -Path "$output\version.xml" 81 | } 82 | 83 | Task BuildPSD1 -inputs (Get-ChildItem $Source -Recurse -File) -Outputs $ManifestPath { 84 | Write-Output " Update [$ManifestPath]" 85 | Copy-Item "$source\$ModuleName.psd1" -Destination $ManifestPath 86 | 87 | $bumpVersionType = 'Patch' 88 | 89 | #$functions = Get-ChildItem "$ModuleName\public\*.ps1" | Where-Object { $_.name -notmatch 'tests' } | Select-Object -ExpandProperty basename 90 | 91 | #$oldFunctions = (Get-Metadata -Path $manifestPath -PropertyName 'FunctionsToExport') 92 | 93 | #$functions | Where-Object { $_ -notin $oldFunctions } | ForEach-Object { $bumpVersionType = 'Minor' } 94 | #$oldFunctions | Where-Object { $_ -notin $Functions } | ForEach-Object { $bumpVersionType = 'Major' } 95 | 96 | #Set-ModuleFunctions -Name $ManifestPath -FunctionsToExport $functions 97 | 98 | # Bump the module version 99 | $version = [version] (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion') 100 | $galleryVersion = Import-Clixml -Path "$output\version.xml" 101 | if ( $version -lt $galleryVersion ) { 102 | $version = $galleryVersion 103 | } 104 | Write-Output " Stepping [$bumpVersionType] version [$version]" 105 | $version = [version] (Step-Version $version -Type $bumpVersionType) 106 | Write-Output " Using version: $version" 107 | 108 | Update-Metadata -Path $ManifestPath -PropertyName ModuleVersion -Value $version 109 | } 110 | 111 | Task UpdateSource { 112 | Copy-Item $ManifestPath -Destination "$source\$ModuleName.psd1" 113 | } 114 | 115 | Task ImportModule { 116 | if ( -Not ( Test-Path $ManifestPath ) ) { 117 | Write-Output " Modue [$ModuleName] is not built, cannot find [$ManifestPath]" 118 | Write-Error "Could not find module manifest [$ManifestPath]. You may need to build the module first" 119 | } 120 | else { 121 | if (Get-Module $ModuleName) { 122 | Write-Output " Unloading Module [$ModuleName] from previous import" 123 | Remove-Module $ModuleName 124 | } 125 | Write-Output " Importing Module [$ModuleName] from [$ManifestPath]" 126 | Import-Module $ManifestPath -Force 127 | } 128 | } 129 | 130 | Task Publish { 131 | # Gate deployment 132 | if ( 133 | $ENV:BHBuildSystem -ne 'Unknown' -and 134 | $ENV:BHBranchName -eq "master" -and 135 | $ENV:BHCommitMessage -match '!deploy' 136 | ) { 137 | $Params = @{ 138 | Path = $BuildRoot 139 | Force = $true 140 | } 141 | 142 | Invoke-PSDeploy @Verbose @Params 143 | } 144 | else { 145 | "Skipping deployment: To deploy, ensure that...`n" + 146 | "`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" + 147 | "`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" + 148 | "`t* Your commit message includes !deploy (Current: $ENV:BHCommitMessage)" 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxmoxcli", 3 | "version": "1.0.0", 4 | "description": "A Proxmox PowerShell module for Proxmox APIs", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/quonic/ProxmoxCLI.git" 16 | }, 17 | "keywords": [ 18 | "proxmox", 19 | "powershell" 20 | ], 21 | "author": "Jim Caten", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/quonic/ProxmoxCLI/issues" 25 | }, 26 | "homepage": "https://github.com/quonic/ProxmoxCLI#readme" 27 | } 28 | -------------------------------------------------------------------------------- /spec/module.Steps.ps1: -------------------------------------------------------------------------------- 1 | Given 'the module was named (\S*)' { 2 | Param($Name) 3 | $Script:ModuleName = $Name 4 | 5 | $path = "$PSScriptRoot\.." 6 | $module = Get-ChildItem -Path $path -Recurse -Filter "$ModuleName.psm1" -verbose 7 | $module | Should not benullorempty 8 | $module.fullname | Should Exist 9 | 10 | $Script:ModuleSource = "$PSScriptRoot\..\$ModuleName" 11 | $Script:ModuleOutput = "$PSScriptRoot\..\Output\$ModuleName" 12 | } 13 | 14 | Given 'we use the (\S*) root folder' { 15 | Param($root) 16 | Switch ($root) { 17 | 'Project' { 18 | $script:BaseFolder = Resolve-Path "$PSScriptRoot\.." | Select-Object -ExpandProperty Path 19 | } 20 | 'ModuleSource' { 21 | $script:BaseFolder = $Script:ModuleSource 22 | } 23 | 'ModuleOutput' { 24 | $script:BaseFolder = $Script:ModuleOutput 25 | } 26 | } 27 | } 28 | 29 | Then 'it (will have|had) a (?\S*) (file|folder).*' { 30 | Param($Path) 31 | 32 | Join-Path $script:BaseFolder $Path | Should Exist 33 | } 34 | 35 | When 'the module (is|can be) imported' { 36 | { Import-Module $ModuleOutput -Force } | Should Not Throw 37 | } 38 | 39 | Then 'Get-Module will show the module' { 40 | Get-Module -Name $ModuleName | Should Not BeNullOrEmpty 41 | } 42 | 43 | Then 'Get-Command will list functions' { 44 | Get-Command -Module $ModuleName | Should Not BeNullOrEmpty 45 | } 46 | 47 | Then '(function )?(?\S*) will be listed in module manifest' { 48 | Param($Function) 49 | (Get-Content $ModuleSource\$ModuleName.psd1 -Raw) -match [regex]::Escape($Function) | Should Be $true 50 | } 51 | 52 | Then '(function )?(?\S*) will contain (?.*)' { 53 | Param($Function, $Text) 54 | $Command = Get-Command $Function -Module $ModuleName 55 | $match = [regex]::Escape($Text) 56 | ($Command.Definition -match $match ) | Should Be True 57 | } 58 | 59 | Then '(function )?(?\S*) will have comment based help' { 60 | Param($Function) 61 | $help = Get-Help $Function 62 | $help | Should Not BeNullOrEmpty 63 | } 64 | 65 | Then 'will have readthedoc pages' { 66 | { Invoke-WebRequest -uri "https://$ModuleName.readthedocs.io" -UseBasicParsing } | Should Not Throw 67 | } 68 | 69 | Then '(function )?(?\S*) will have a feature specification or a pester test' { 70 | param($Function) 71 | 72 | $file = Get-ChildItem -Path $PSScriptRoot\.. -Include "$Function.feature", "$Function.Tests.ps1" -Recurse 73 | $file.fullname | Should Not BeNullOrEmpty 74 | } 75 | 76 | Then 'all public functions (?.*)' { 77 | Param($Action) 78 | $step = @{keyword = 'Then' } 79 | $AllPassed = $true 80 | foreach ($command in (Get-Command -Module $ModuleName )) { 81 | $step.text = ('function {0} {1}' -f $command.Name, $Action ) 82 | 83 | Invoke-GherkinStep $step -Pester $Pester -Visible 84 | If ( -Not $Pester.TestResult[-1].Passed ) { 85 | $AllPassed = $false 86 | } 87 | 88 | $step.keyword = 'And' 89 | } 90 | $AllPassed | Should be $true 91 | } 92 | 93 | Then 'will be listed in the PSGallery' { 94 | Find-Module $ModuleName | Should Not BeNullOrEmpty 95 | } 96 | 97 | Given 'we have (?(public)) functions?' { 98 | param($folder) 99 | "$psscriptroot\..\*\$folder\*.ps1" | Should Exist 100 | } 101 | 102 | Then 'all script files pass PSScriptAnalyzer rules' { 103 | 104 | $Rules = Get-ScriptAnalyzerRule 105 | $scripts = Get-ChildItem $BaseFolder -Include *.ps1, *.psm1, *.psd1 -Recurse | Where-Object fullname -notmatch 'classes' 106 | 107 | $AllPassed = $true 108 | 109 | foreach ($Script in $scripts ) { 110 | $file = $script.fullname.replace($BaseFolder, '$') 111 | 112 | 113 | Context $file { 114 | 115 | foreach ( $rule in $rules ) { 116 | It " [$file] Rule [$rule]" { 117 | 118 | (Invoke-ScriptAnalyzer -Path $script.FullName -IncludeRule $rule.RuleName ).Count | Should Be 0 119 | } 120 | } 121 | } 122 | 123 | If ( -Not $Pester.TestResult[-1].Passed ) { 124 | $AllPassed = $false 125 | } 126 | } 127 | 128 | $AllPassed | Should be $true 129 | } 130 | -------------------------------------------------------------------------------- /spec/module.feature: -------------------------------------------------------------------------------- 1 | Feature: A proper community module 2 | As a module owner 3 | In order to have a good community module 4 | I want to make sure everything works and the quality is high 5 | 6 | Background: we have a module 7 | Given the module was named ProxmoxCLI 8 | 9 | 10 | Scenario: Should have correct project structure and files 11 | Given we use the project root folder 12 | Then it will have a readme.md file for general information 13 | And it will have a LICENSE file 14 | And it will have a tests\*.Tests.ps1 file for Pester 15 | And it will have a spec\*.feature file for Gherkin 16 | And it will have a spec\*.Steps.ps1 file for Gherkin 17 | And it will have a build.ps1 file for builds 18 | And it will have a *.build.ps1 file for builds 19 | And it will have a *.PSDeploy.ps1 file for deployments 20 | And it will have a .gitignore file to ignore build artifacts 21 | And it will have a appveyor.yml file for build automation 22 | 23 | 24 | Scenario: Should have correct module structure in source 25 | Given we use the ModuleSource root folder 26 | Then it will have a *.psd1 file for module manifest 27 | And it will have a *.psm1 file for module 28 | And it will have a public folder for public functions 29 | And it will have a public\*.ps1 file for a public function 30 | And it will have a private folder for private functions 31 | And it will have a private\*.ps1 file for a private function 32 | 33 | Scenario: Should have correct module structure in source 34 | Given we use the ModuleOutput root folder 35 | Then it will have a *.psd1 file for module manifest 36 | And it will have a *.psm1 file for module 37 | 38 | Scenario: the module source should import 39 | Given we use the ModuleSource root folder 40 | And it had a *.psd1 file 41 | When the module is imported 42 | Then Get-Module will show the module 43 | And Get-Command will list functions 44 | 45 | Scenario: The built Module should import 46 | Given we use the ModuleOutput root folder 47 | And it had a *.psd1 file 48 | When the module is imported 49 | Then Get-Module will show the module 50 | And Get-Command will list functions 51 | 52 | Scenario: Public function features 53 | Given the module is imported 54 | And we have public functions 55 | Then all public functions will be listed in module manifest 56 | And all public functions will contain cmdletbinding 57 | And all public functions will contain ThrowTerminatingError 58 | 59 | Scenario: Should be well documented 60 | Given the module is imported 61 | And we use the project root folder 62 | And we have public functions 63 | Then it will have a readme.md file for general information 64 | And all public functions will have comment based help 65 | And function Node will have a feature specification or a pester test 66 | And all public functions will have a feature specification or a pester test 67 | And will have readthedoc pages 68 | And it will have a PITCHME.md file for project promotion 69 | 70 | @PSScriptAnalyzer @Slow 71 | Scenario: Should pass PSScriptAnalyzer rules 72 | Given we use the ModuleSource root folder 73 | Then it will have a public\*.ps1 file for a public function 74 | And all script files pass PSScriptAnalyzer rules 75 | When we use the ModuleOutput root folder 76 | Then all script files pass PSScriptAnalyzer rules 77 | 78 | @Slow 79 | Scenario: Should be published 80 | Given the module can be imported 81 | Then will be listed in the PSGallery 82 | 83 | --------------------------------------------------------------------------------