From 50c048eb349bec1379e59777465a492bbfb548b9 Mon Sep 17 00:00:00 2001 From: Arnike Date: Wed, 30 Nov 2022 16:53:56 +0300 Subject: [PATCH] Initial commit --- README.md | 175 +++++++++++++++++- functions/private/Invoke-PSIPAMRequest.ps1 | 64 +++++++ functions/private/Test-PSIPAMSession.ps1 | 15 ++ functions/private/Update-PSIPAMSession.ps1 | 18 ++ functions/public/Get-PSIPAMAddress.ps1 | 70 +++++++ functions/public/Get-PSIPAMFirstFreeIP.ps1 | 33 ++++ functions/public/Get-PSIPAML2Domain.ps1 | 26 +++ functions/public/Get-PSIPAMNameserver.ps1 | 29 +++ functions/public/Get-PSIPAMSection.ps1 | 32 ++++ functions/public/Get-PSIPAMSubnet.ps1 | 129 +++++++++++++ functions/public/Get-PSIPAMSubnetUsage.ps1 | 33 ++++ functions/public/Get-PSIPAMTags.ps1 | 26 +++ functions/public/Get-PSIPAMVlan.ps1 | 45 +++++ functions/public/Get-PSIPAMVrf.ps1 | 26 +++ functions/public/New-PSIPAMAddress.ps1 | 182 +++++++++++++++++++ functions/public/New-PSIPAMFirstFreeIP.ps1 | 172 ++++++++++++++++++ functions/public/New-PSIPAMSession.ps1 | 37 ++++ functions/public/New-PSIPAMSubnet.ps1 | 202 +++++++++++++++++++++ functions/public/Remove-PSIPAMAddress.ps1 | 35 ++++ functions/public/Set-PSIPAMAddress.ps1 | 172 ++++++++++++++++++ functions/public/Set-PSIPAMSubnet.ps1 | 0 images/logo.png | Bin 0 -> 43318 bytes ps.ipam.code-workspace | 8 + ps.ipam.psd1 | Bin 0 -> 5904 bytes ps.ipam.psm1 | 6 + 25 files changed, 1534 insertions(+), 1 deletion(-) create mode 100644 functions/private/Invoke-PSIPAMRequest.ps1 create mode 100644 functions/private/Test-PSIPAMSession.ps1 create mode 100644 functions/private/Update-PSIPAMSession.ps1 create mode 100644 functions/public/Get-PSIPAMAddress.ps1 create mode 100644 functions/public/Get-PSIPAMFirstFreeIP.ps1 create mode 100644 functions/public/Get-PSIPAML2Domain.ps1 create mode 100644 functions/public/Get-PSIPAMNameserver.ps1 create mode 100644 functions/public/Get-PSIPAMSection.ps1 create mode 100644 functions/public/Get-PSIPAMSubnet.ps1 create mode 100644 functions/public/Get-PSIPAMSubnetUsage.ps1 create mode 100644 functions/public/Get-PSIPAMTags.ps1 create mode 100644 functions/public/Get-PSIPAMVlan.ps1 create mode 100644 functions/public/Get-PSIPAMVrf.ps1 create mode 100644 functions/public/New-PSIPAMAddress.ps1 create mode 100644 functions/public/New-PSIPAMFirstFreeIP.ps1 create mode 100644 functions/public/New-PSIPAMSession.ps1 create mode 100644 functions/public/New-PSIPAMSubnet.ps1 create mode 100644 functions/public/Remove-PSIPAMAddress.ps1 create mode 100644 functions/public/Set-PSIPAMAddress.ps1 create mode 100644 functions/public/Set-PSIPAMSubnet.ps1 create mode 100644 images/logo.png create mode 100644 ps.ipam.code-workspace create mode 100644 ps.ipam.psd1 create mode 100644 ps.ipam.psm1 diff --git a/README.md b/README.md index 1c2bf86..8e92412 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,175 @@ -# ps.ipam + + + + + + + + +
+
+ + Logo + + +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) +![Powershell](https://img.shields.io/badge/powershell-v5.1+-blue.svg) + +

PS.IPAM

+ +

+ Powershell module for phpIPAM +
+ Explore the docs » +
+
+ Report Bug + · + Request Feature +

+
+ + + + +
+ Table of Contents +
    +
  1. + About The Project +
  2. +
  3. + Getting Started + +
  4. +
  5. Usage
  6. +
  7. Roadmap
  8. +
  9. Contributing
  10. +
  11. License
  12. +
  13. Contact
  14. +
  15. Links
  16. +
+
+ + + + +## About The Project +PS.IPAM is Powershell module that wraps phpIPAM RESTful APi into cmdlets +Use the `README.md` to get started. + +

(back to top)

+ + + + +## Getting Started + +### Installation +* **PSGallery** + ```sh + Install-Module -Name ps.ipam + ``` +* **Offline** + 1. Unblock the Internet-downloaded NuGet package (`.nupkg`) file, for example using `Unblock-File -Path C:\Downloads\module.nupkg` cmdlet. + 2. Extract the contents of the NuGet package to a local folder. + 3. Delete the NuGet-specific elements from the folder. + 4. Rename the folder. The default folder name is usually `.`. The version can include `-prerelease` if the module is tagged as a prerelease version. Rename the folder to just the module name. For example, `azurerm.storage.5.0.4-preview` becomes `azurerm.storage`. + 5. Copy the folder to one of the folders in the `$env:PSModulePath value`. `$env:PSModulePath` is a semicolon-delimited set of paths in which PowerShell should look for modules. + +

(back to top)

+ + + + +## Usage + +At first you have to create new session: +```sh +New-PSIPAMSession -URL -AppID -Credentials +``` + +_For more examples, please refer to the [Documentation](https://git.arnike.ru/Arnike/ps.ipam/wiki)_ + +

(back to top)

+ + + + +## Roadmap + +- [ ] Implement all **Set** functions + +See the [open issues](https://git.arnike.ru/Arnike/ps.ipam/issues) for a full list of proposed features (and known issues). + +

(back to top)

+ + + + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". +Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +

(back to top)

+ + + + +## License + +Distributed under the GNU GPL 2.0 or later License. See `LICENSE` for more information. + +

(back to top)

+ + + + +## Contact + +Nikolay Tatarinov - arnikes@gmail.com + +Project Link: [https://git.arnike.ru/Arnike/ps.ipam](https://git.arnike.ru/Arnike/ps.ipam) + +

(back to top)

+ + + + +## Links + +* [phpIPAM API Documentation](https://phpipam.net/api/api_documentation/) +* [PSGallery](https://www.powershellgallery.com/packages/ps.ipam/1.0) + +

(back to top)

+ + + + + +[logo]: images/logo.png diff --git a/functions/private/Invoke-PSIPAMRequest.ps1 b/functions/private/Invoke-PSIPAMRequest.ps1 new file mode 100644 index 0000000..00040bf --- /dev/null +++ b/functions/private/Invoke-PSIPAMRequest.ps1 @@ -0,0 +1,64 @@ +function Invoke-PSIPAMRequest { + [CmdletBinding()] + param ( + [parameter(Mandatory=$true)] + [ValidateSet("POST","GET","PATCH","DELETE")] + [string] + $Method, + [parameter(Mandatory=$true)] + [ValidateSet("user","vlan","subnets","addresses","sections","vrf","l2domains","tools")] + [string] + $Controller, + [parameter(Mandatory=$false)] + [ValidateSet("nameservers")] + [string] + $SubController, + [Parameter(Mandatory=$false)] + [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] + $Params, + [Parameter(Mandatory=$false)] + [array] + $Identifiers + ) + $_tokenStatus = Test-PSIPAMSession + + if ($_tokenStatus -eq "NoToken") { throw "No session available!" } + if ($_tokenStatus -eq "Expired") { Update-PSIPAMSession } + + $Controller = $Controller.ToLower() + + $_uri = "$($script:ipamURL)/api/$($script:ipamAppID)/$Controller" + if ($SubController) { $_uri += "/$SubController" } + if ($Identifiers) { $_uri += "/$($Identifiers -join '/')/" } + + $_headers = @{ + "Accept" = "application/json" + "Content-Type" = "application/json" + "token" = $script:ipamToken + } + + $_arguments = @{ + Method = $Method + Uri = $_uri + Headers = $_headers + } + + if ($Method -match "POST|PATCH") { + if ($Params -is [PSCustomObject]) { + $_params = @{}; + $Params | Get-Member -MemberType *Property | Where-Object { + $_params.($_.name) = $Params.($_.name) + } + } else { $_params = $Params } + + $_arguments.Add("Body",($_params | ConvertTo-Json)) + } + + $_response = Invoke-RestMethod @_arguments + + if ($_response.code -match "20\d") { + return $_response.data + } else { + throw ("Error - $($_response.code)") + } +} \ No newline at end of file diff --git a/functions/private/Test-PSIPAMSession.ps1 b/functions/private/Test-PSIPAMSession.ps1 new file mode 100644 index 0000000..4c4cf77 --- /dev/null +++ b/functions/private/Test-PSIPAMSession.ps1 @@ -0,0 +1,15 @@ +function Test-PSIPAMSession { + [CmdletBinding()] + param ( + + ) + if ($script:ipamToken) { + if ($script:ipamExpires -lt (Get-Date)) { + return "Expired" + } else { + return "Valid" + } + } else { + return "NoToken" + } +} \ No newline at end of file diff --git a/functions/private/Update-PSIPAMSession.ps1 b/functions/private/Update-PSIPAMSession.ps1 new file mode 100644 index 0000000..d8ade10 --- /dev/null +++ b/functions/private/Update-PSIPAMSession.ps1 @@ -0,0 +1,18 @@ +function Update-PSIPAMSession { + [CmdletBinding()] + param ( + [switch] + $Force + ) + $_tokenStatus = Test-PSIPAMSession + if ($_tokenStatus -eq "NoToken") { + throw "No session available!" + } + if ($_tokenStatus -eq "Valid") { + return (Invoke-PSIPAMRequest -Method PATCH -Controller user).expires + } + if ($_tokenStatus -eq "Expired" -or $Force) { + New-PSIPAMSession -URL $script:ipamURL -AppID $script:ipamAppID -Credentials $script:ipamCredentials + return $script:ipamExpires + } +} \ No newline at end of file diff --git a/functions/public/Get-PSIPAMAddress.ps1 b/functions/public/Get-PSIPAMAddress.ps1 new file mode 100644 index 0000000..252536c --- /dev/null +++ b/functions/public/Get-PSIPAMAddress.ps1 @@ -0,0 +1,70 @@ +function Get-PSIPAMAddress { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByIP")] + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=1,ParameterSetName="BySubnetId")] + [ValidateScript({[ipaddress] $_})] + [ValidateNotNullOrEmpty()] + [string] + $IP, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByHostName")] + [ValidateNotNullOrEmpty()] + [string] + $HostName, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByTag")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $TagId, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="BySubnetId")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SubnetId, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="BySubnetCIDR")] + [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] + [ValidateNotNullOrEmpty()] + [string] + $SubnetCIDR + ) + process { + [string[]]$visiblePropertiesList = @('Id','Ip','Hostname','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "addresses" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_identifiers = @($id) } + "ByIP" { $_identifiers = ("search",$IP) } + "ByHostName" { $_identifiers = ("search_hostname",$HostName) } + "ByTag" { $_identifiers = ("tags",$TagId,"addresses") } + "BySubnetId" { + if ($IP) { + $_identifiers = ($IP,$SubnetId) + } else { + $_params.Item("Controller") = "subnets" + $_identifiers = ($SubnetId,"addresses") + } + } + "BySubnetCIDR" { + $_params.Item("Controller") = "subnets" + $_subnetId = (Get-PSIPAMSubnet -CIDR $SubnetCIDR).id + if (!$_subnetId) { throw "Cannot find subnet!" } + + $_identifiers = ($_subnetId,"addresses") + } + } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAMAddress \ No newline at end of file diff --git a/functions/public/Get-PSIPAMFirstFreeIP.ps1 b/functions/public/Get-PSIPAMFirstFreeIP.ps1 new file mode 100644 index 0000000..47215d2 --- /dev/null +++ b/functions/public/Get-PSIPAMFirstFreeIP.ps1 @@ -0,0 +1,33 @@ +function Get-PSIPAMFirstFreeIP { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByCIDR")] + [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] + [ValidateNotNullOrEmpty()] + [string] + $CIDR, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + $_params = @{ + Controller = "subnets" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_subnetId = $Id } + "ByCIDR" { + $_subnetId = (Get-PSIPAMSubnet -CIDR $CIDR).id + if (!$_subnetId) { throw "Cannot find subnet!" } + } + } + $_identifiers = @($_subnetId,"first_free") + $_params.Add("Identifiers",$_identifiers) + + return Invoke-PSIPAMRequest @_params | Select-Object @{n="Ip";e={$_}} + } +} +Export-ModuleMember -Function Get-PSIPAMFirstFreeIP \ No newline at end of file diff --git a/functions/public/Get-PSIPAML2Domain.ps1 b/functions/public/Get-PSIPAML2Domain.ps1 new file mode 100644 index 0000000..2a20537 --- /dev/null +++ b/functions/public/Get-PSIPAML2Domain.ps1 @@ -0,0 +1,26 @@ +function Get-PSIPAML2Domain { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "l2domains" + Method = "GET" + } + $_identifiers = @($Id) + + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAML2Domain \ No newline at end of file diff --git a/functions/public/Get-PSIPAMNameserver.ps1 b/functions/public/Get-PSIPAMNameserver.ps1 new file mode 100644 index 0000000..8c6fa3b --- /dev/null +++ b/functions/public/Get-PSIPAMNameserver.ps1 @@ -0,0 +1,29 @@ +function Get-PSIPAMNameserver { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name','Address','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "tools" + SubController = "nameservers" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_nameserverId = $Id } + } + $_identifiers = @($_nameserverId) + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | Select-Object @{n="address";e={$_.namesrv1}},* | Select-Object -ExcludeProperty namesrv1 | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAMNameserver \ No newline at end of file diff --git a/functions/public/Get-PSIPAMSection.ps1 b/functions/public/Get-PSIPAMSection.ps1 new file mode 100644 index 0000000..0894b1d --- /dev/null +++ b/functions/public/Get-PSIPAMSection.ps1 @@ -0,0 +1,32 @@ +function Get-PSIPAMSection { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0,ParameterSetName="ByName")] + [ValidateNotNullOrEmpty()] + [string] + $Name + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "sections" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_identifiers = @($Id) } + "ByName" { $_identifiers = @($Name) } + } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember Get-PSIPAMSection \ No newline at end of file diff --git a/functions/public/Get-PSIPAMSubnet.ps1 b/functions/public/Get-PSIPAMSubnet.ps1 new file mode 100644 index 0000000..0d22635 --- /dev/null +++ b/functions/public/Get-PSIPAMSubnet.ps1 @@ -0,0 +1,129 @@ +function Get-PSIPAMSubnet { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByCIDR")] + [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] + [ValidateNotNullOrEmpty()] + [string] + $CIDR, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="BySectionId")] + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=2,ParameterSetName="ByVlanNumber")] + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=1,ParameterSetName="ByVlanId")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SectionId, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="BySectionName")] + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=3,ParameterSetName="ByVlanNumber")] + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=2,ParameterSetName="ByVlanId")] + [ValidateNotNullOrEmpty()] + [string] + $SectionName, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByVrfId")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $VrfId, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByVlanId")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $VlanId, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByVlanNumber")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $VlanNumber, + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=1,ParameterSetName="ByVlanNumber")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $VlanDomain, + [parameter(Mandatory=$false,ParameterSetName="ByID")] + [switch] + $Slaves, + [parameter(Mandatory=$false,ParameterSetName="ByID")] + [switch] + $Recurse + ) + process { + [string[]]$visiblePropertiesList = @('Id','Subnet','Mask','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "subnets" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByCIDR" { + $_identifiers = @("cidr",$CIDR) + } + "ByID" { + $_identifiers = @($Id) + + if ($Slaves) { + if ($Recurse) { + $_identifiers += "slaves_recursive" + } else { + $_identifiers += "slaves" + } + } + } + "BySectionId" { + $_params.Item("Controller") = "sections" + $_sectionId = $SectionId + + $_identifiers = @($_sectionId,"subnets") + } + "BySectionName" { + $_params.Item("Controller") = "sections" + $_sectionId = (Get-PSIPAMSection -Name $SectionName).id + if (!$_sectionId) { throw "Cannot find section!" } + + $_identifiers = @($_sectionId,"subnets") + } + "ByVrfId" { + $_params.Item("Controller") = "vrf" + $_vrfId = $VrfId + + $_identifiers = @($_vrfId,"subnets") + } + "ByVlanId" { + $_params.Item("Controller") = "vlan" + $_vlanId = $VlanId + if ($SectionId) { $_sectionId = $SectionId } + if ($SectionName){ $_sectionId = (Get-PSIPAMSection -Name $SectionName).id } + + $_identifiers = @($_vlanId,"subnets") + + if ($_sectionId) { $_identifiers += $_sectionId } + } + "ByVlanNumber" { + $_params.Item("Controller") = "vlan" + $_vlans = Get-PSIPAMVlan -Number $VlanNumber + if ($VlanDomain) { $_vlans = $_vlans | Where-Object {$_.domainId -eq $VlanDomain} } + if ($SectionId) { $_sectionId = $SectionId } + if ($SectionName){ $_sectionId = (Get-PSIPAMSection -Name $SectionName).id } + + $_vlanId = $_vlans.vlanId + + if ($_vlanid -is [System.Array]) { throw "More than one vLan with $VlanNumber number is present!" } + if (!$_vlanId) { throw "Cannot find Vlan!"} + + $_identifiers = @($_vlanId,"subnets") + + if ($_sectionId) { $_identifiers += $_sectionId } + } + } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember Get-PSIPAMSubnet \ No newline at end of file diff --git a/functions/public/Get-PSIPAMSubnetUsage.ps1 b/functions/public/Get-PSIPAMSubnetUsage.ps1 new file mode 100644 index 0000000..7b8b550 --- /dev/null +++ b/functions/public/Get-PSIPAMSubnetUsage.ps1 @@ -0,0 +1,33 @@ +function Get-PSIPAMSubnetUsage { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByCIDR")] + [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] + [ValidateNotNullOrEmpty()] + [string] + $CIDR, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + $_params = @{ + Controller = "subnets" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByCIDR" { + $_subnetId = (Get-PSIPAMSubnet -CIDR $CIDR).id + if (!$_subnetId) { throw "Cannot find subnet!" } + } + "ByID" { $_subnetId = $Id } + } + $_identifiers = @($_subnetId,"usage") + $_params.Add("Identifiers",$_identifiers) + + return Invoke-PSIPAMRequest @_params + } +} +Export-ModuleMember -Function Get-PSIPAMSubnetUsage \ No newline at end of file diff --git a/functions/public/Get-PSIPAMTags.ps1 b/functions/public/Get-PSIPAMTags.ps1 new file mode 100644 index 0000000..cd8f0ec --- /dev/null +++ b/functions/public/Get-PSIPAMTags.ps1 @@ -0,0 +1,26 @@ +function Get-PSIPAMTags { + [CmdletBinding()] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "addresses" + Method = "GET" + } + $_identifiers = @("tags") + if ($Id) { $_identifiers += $Id } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAMTags \ No newline at end of file diff --git a/functions/public/Get-PSIPAMVlan.ps1 b/functions/public/Get-PSIPAMVlan.ps1 new file mode 100644 index 0000000..60634b2 --- /dev/null +++ b/functions/public/Get-PSIPAMVlan.ps1 @@ -0,0 +1,45 @@ +function Get-PSIPAMVlan { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByNumber")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Number, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByL2Domain")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $L2DomainId + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "vlan" + Method = "GET" + } + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_identifiers = @($Id) } + "ByNumber" { $_identifiers = @("search",$Number) } + "ByL2Domain"{ + $_params.Item("Controller") = "l2domains" + + $_l2domainId = $L2DomainId + + $_identifiers = @($_l2domainId,"vlans") + } + } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | Select-Object @{n="id";e={$_.vlanId}},* | Select-Object -ExcludeProperty vlanId | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAMVlan \ No newline at end of file diff --git a/functions/public/Get-PSIPAMVrf.ps1 b/functions/public/Get-PSIPAMVrf.ps1 new file mode 100644 index 0000000..6846a4c --- /dev/null +++ b/functions/public/Get-PSIPAMVrf.ps1 @@ -0,0 +1,26 @@ +function Get-PSIPAMVrf { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id + ) + process { + [string[]]$visiblePropertiesList = @('Id','Name','Description') + $visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visiblePropertiesList) + + $_params = @{ + Controller = "vrf" + Method = "GET" + } + if ($Id) { $_identifiers = @($Id) } + + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params | Select-Object @{n="id";e={$_.vrfId}},* | Select-Object -ExcludeProperty vrfId | ` + Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru + } +} +Export-ModuleMember -Function Get-PSIPAMVrf \ No newline at end of file diff --git a/functions/public/New-PSIPAMAddress.ps1 b/functions/public/New-PSIPAMAddress.ps1 new file mode 100644 index 0000000..0a70bfa --- /dev/null +++ b/functions/public/New-PSIPAMAddress.ps1 @@ -0,0 +1,182 @@ +function New-PSIPAMAddress { + [CmdletBinding()] + param ( + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=0)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SubnetId, + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="IP address", + Position=1)] + [ValidateScript({[ipaddress] $_ })] + [ValidateNotNullOrEmpty()] + [string] + $Ip, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Defines if address is presented as gateway", + Position=2)] + [switch] + $Gateway, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address description", + Position=3)] + [ValidateNotNullOrEmpty()] + [string] + $Description, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address hostname", + Position=4)] + [ValidateNotNullOrEmpty()] + [string] + $Hostname, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Mac address", + Position=5)] + [ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })] + [ValidateNotNullOrEmpty()] + [string] + $MAC, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address owner", + Position=6)] + [ValidateNotNullOrEmpty()] + [string] + $Owner, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=7)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $TagId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if PTR should not be created", + Position=8)] + [switch] + $PTRIgnore, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of PowerDNS PTR record", + Position=7)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $PTRId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Note", + Position=10)] + [ValidateNotNullOrEmpty()] + [string] + $Note, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Exclude this address from status update scans (ping)", + Position=11)] + [switch] + $ExcludePing, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of device address belongs to", + Position=12)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $DeviceId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Port", + Position=13)] + [ValidateNotNullOrEmpty()] + [string] + $Port, + + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] + $CustomFields + ) + process { + $_params = @{ + Controller = "addresses" + Method = "POST" + } + + $_body = @{ + subnetId = $SubnetId + ip = $Ip + } + if ($Gateway) { $_body.Add("is_gateway", "1") } + if ($Description) { $_body.Add("description", $Description) } + if ($Hostname) { $_body.Add("hostname", $Hostname) } + if ($MAC) { $_body.Add("mac", $MAC) } + if ($Owner) { $_body.Add("owner", $Owner) } + if ($TagId) { $_body.Add("tag", $TagId) } + if ($PTRIgnore) { $_body.Add("PTRignore", "1") } + if ($PTRId) { $_body.add("PTR", $PTRId)} + if ($Note) { $_body.Add("note", $Note) } + if ($ExcludePing) { $_body.Add("excludePing", "1") } + if ($DeviceId) { $_body.Add("deviceId", $DeviceId) } + if ($Port) { $_body.Add("port", $Port) } + + if ($CustomFields) { + if ($CustomFields -is [PSCustomObject]) { + $_customFields = @{}; + $CustomFields | Get-Member -MemberType *Property | Where-Object { + $_customFields.($_.name) = $CustomFields.($_.name) + } + } else { $_customFields = $CustomFields } + + $_body = $_body + $_customFields + } + + $_params.Add("Params",$_body) + + try { + Invoke-PSIPAMRequest @_params + } + finally { + Get-PSIPAMAddress -SubnetId $SubnetId -Ip $Ip + } + } +} +Export-ModuleMember -Function New-PSIPAMAddress \ No newline at end of file diff --git a/functions/public/New-PSIPAMFirstFreeIP.ps1 b/functions/public/New-PSIPAMFirstFreeIP.ps1 new file mode 100644 index 0000000..fe377dc --- /dev/null +++ b/functions/public/New-PSIPAMFirstFreeIP.ps1 @@ -0,0 +1,172 @@ +function New-PSIPAMFirstFreeIP { + [CmdletBinding()] + param ( + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=0)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SubnetId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Defines if address is presented as gateway", + Position=2)] + [switch] + $Gateway, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address description", + Position=3)] + [ValidateNotNullOrEmpty()] + [string] + $Description, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address hostname", + Position=4)] + [ValidateNotNullOrEmpty()] + [string] + $Hostname, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Mac address", + Position=5)] + [ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })] + [ValidateNotNullOrEmpty()] + [string] + $MAC, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address owner", + Position=6)] + [ValidateNotNullOrEmpty()] + [string] + $Owner, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=7)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $TagId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if PTR should not be created", + Position=8)] + [switch] + $PTRIgnore, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of PowerDNS PTR record", + Position=9)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $PTRId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Note", + Position=10)] + [ValidateNotNullOrEmpty()] + [string] + $Note, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Exclude this address from status update scans (ping)", + Position=11)] + [switch] + $ExcludePing, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of device address belongs to", + Position=12)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $DeviceId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Port", + Position=13)] + [ValidateNotNullOrEmpty()] + [string] + $Port, + + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] + $CustomFields + ) + process { + $_params = @{ + Controller = "addresses" + Method = "POST" + } + $_identifiers = @('first_free') + + $_params.Add("Identifiers",$_identifiers) + + $_body = @{ + subnetId = $SubnetId + } + if ($Gateway) { $_body.Add("is_gateway", "1") } + if ($Description) { $_body.Add("description", $Description) } + if ($Hostname) { $_body.Add("hostname", $Hostname) } + if ($MAC) { $_body.Add("mac", $MAC) } + if ($Owner) { $_body.Add("owner", $Owner) } + if ($TagId) { $_body.Add("tag", $TagId) } + if ($PTRIgnore) { $_body.Add("PTRignore", "1") } + if ($PTRId) { $_body.add("PTR", $PTRId)} + if ($Note) { $_body.Add("note", $Note) } + if ($ExcludePing) { $_body.Add("excludePing", "1") } + if ($DeviceId) { $_body.Add("deviceId", $DeviceId) } + if ($Port) { $_body.Add("port", $Port) } + + if ($CustomFields) { + if ($CustomFields -is [PSCustomObject]) { + $_customFields = @{}; + $CustomFields | Get-Member -MemberType *Property | Where-Object { + $_customFields.($_.name) = $CustomFields.($_.name) + } + } else { $_customFields = $CustomFields } + + $_body = $_body + $_customFields + } + + $_params.Add("Params",$_body) + + $_result = Invoke-PSIPAMRequest @_params + if ($_result) { + Get-PSIPAMAddress -SubnetId $SubnetId -IP $_result + } + } +} +Export-ModuleMember -Function New-PSIPAMFirstFreeIP \ No newline at end of file diff --git a/functions/public/New-PSIPAMSession.ps1 b/functions/public/New-PSIPAMSession.ps1 new file mode 100644 index 0000000..119a59e --- /dev/null +++ b/functions/public/New-PSIPAMSession.ps1 @@ -0,0 +1,37 @@ +function New-PSIPAMSession { + [CmdletBinding()] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)] + [ValidateNotNullOrEmpty()] + [string]$URL, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=1)] + [ValidateNotNullOrEmpty()] + [string]$AppID, + [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=2)] + [ValidateNotNullOrEmpty()] + [pscredential]$Credentials + ) + $_bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credentials.Password) + $_password = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($_bstr) + $_uri = "$URL/api/$AppID/user" + $_auth = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Credentials.UserName):$_password")) + $_headers = @{ + "Accept" = "application/json" + "Content-Type" = "application/json" + "Authorization" = "Basic $_auth" + } + + $_response = Invoke-RestMethod -Method Post -Uri $_uri -Headers $_headers -ErrorAction SilentlyContinue + + if ($_response.success -eq $true) { + $script:ipamAuth = $true + $script:ipamToken = $_response.data.token + $script:ipamAppID = $AppID + $script:ipamURL = $URL + $script:ipamCredentials = $Credentials + $script:ipamExpires = Get-Date $_response.data.expires + } else { + $_response.error + } +} +Export-ModuleMember -Function New-PSIPAMSession \ No newline at end of file diff --git a/functions/public/New-PSIPAMSubnet.ps1 b/functions/public/New-PSIPAMSubnet.ps1 new file mode 100644 index 0000000..d655d34 --- /dev/null +++ b/functions/public/New-PSIPAMSubnet.ps1 @@ -0,0 +1,202 @@ +function New-PSIPAMSubnet { + [CmdletBinding()] + param ( + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="CIDR of subnet in dotted format (e.g 10.10.10.0/24)", + Position=0)] + [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] + [ValidateNotNullOrEmpty()] + [string] + $CIDR, + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Section identifier", + Position=1)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SectionId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Subnet description", + Position=2)] + [string] + $Description, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Assigns subnet to VLAN", + Position=3)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $VlanId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Assigns subnet to VRF", + Position=4)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $VrfId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Master subnet id for nested subnet", + Position=5)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $MasterSubnetId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of nameserver to attach to subnet", + Position=6)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $NameserverId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls weather subnet is displayed as IP address or Name in subnets menu", + Position=7)] + [switch] + $ShowName, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if PTR records should be created for subnet", + Position=8)] + [switch] + $DNSRecursive, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls weather hostname DNS records are displayed", + Position=9)] + [switch] + $DNSRecords, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if IP requests are allowed for subnet", + Position=10)] + [switch] + $AllowRequests, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls which scanagent to use for subnet (default id 1)", + Position=11)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $ScanAgentId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if new hosts should be discovered for new host scans", + Position=12)] + [switch] + $DiscoverSubnet, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Marks subnet as used", + Position=12)] + [switch] + $IsFull, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Assignes state (tag) to subnet (default: 1 Used)", + Position=12)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $TagId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Subnet threshold", + Position=13)] + [ValidateScript({ $_ -match "^\d+$" -and $_ -le 100 -and $_ -ge 1 })] + $Threshold, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Location index", + Position=14)] + [ValidateScript({ $_ -match "^\d+$" })] + [string] + $LocationId, + + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] + $CustomFields + ) + process { + $_params = @{ + Controller = "subnets" + Method = "POST" + } + + $_body = @{ + subnet = $CIDR.Split('/')[0] + mask = $CIDR.Split('/')[1] + sectionId = $SectionId + } + if ($Description) { $_body.Add("description", $Description) } + if ($VlanId) { $_body.Add("vlanId", $VlanId) } + if ($VrfId) { $_body.Add("vrfId", $VrfId) } + if ($MasterSubnetId) { $_body.Add("masterSubnetId", $MasterSubnetId) } + if ($NameserverId) { $_body.Add("nameserverId", $NameserverId) } + if ($ShowName) { $_body.Add("showName", "1") } + if ($DNSRecursive) { $_body.Add("DNSrecursive", "1") } + if ($DNSRecords) { $_body.Add("DNSrecords", "1") } + if ($AllowRequests) { $_body.Add("allowRequests", "1") } + if ($ScanAgentId) { $_body.Add("scanAgent", $ScanAgentId) } + if ($DiscoverSubnet) { $_body.Add("discoverSubnet", "1") } + if ($IsFull) { $_body.Add("isFull", "1") } + if ($TagId) { $_body.Add("state", $TagId) } + if ($Threshold) { $_body.Add("threshold", $Threshold) } + if ($Location) { $_body.Add("location", $Location) } + + if ($CustomFields) { + if ($CustomFields -is [PSCustomObject]) { + $_customFields = @{}; + $CustomFields | Get-Member -MemberType *Property | Where-Object { + $_customFields.($_.name) = $CustomFields.($_.name) + } + } else { $_customFields = $CustomFields } + + $_body = $_body + $_customFields + } + + $_params.Add("Params",$_body) + + $_result = Invoke-PSIPAMRequest @_params + if ($_result) { + return Get-PSIPAMSubnet -CIDR $_result + } + } +} +Export-ModuleMember -Function New-PSIPAMSubnet \ No newline at end of file diff --git a/functions/public/Remove-PSIPAMAddress.ps1 b/functions/public/Remove-PSIPAMAddress.ps1 new file mode 100644 index 0000000..49d31b0 --- /dev/null +++ b/functions/public/Remove-PSIPAMAddress.ps1 @@ -0,0 +1,35 @@ +function Remove-PSIPAMAddress { + [CmdletBinding(DefaultParameterSetName="ByID")] + param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0,ParameterSetName="ByID")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0,ParameterSetName="ByIP")] + [ValidateScript({[ipaddress] $_ })] + [ValidateNotNullOrEmpty()] + [string] + $IP, + [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=1,ParameterSetName="ByIP")] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $SubnetId + ) + process { + $_params = @{ + Controller = "addresses" + Method = "DELETE" + } + + switch ($PSCmdlet.ParameterSetName) { + "ByID" { $_identifiers = @($Id) } + "ByIP" { $_identifiers = @($IP,$SubnetId) } + } + $_params.Add("Identifiers",$_identifiers) + + Invoke-PSIPAMRequest @_params + } +} +Export-ModuleMember Remove-PSIPAMAddress \ No newline at end of file diff --git a/functions/public/Set-PSIPAMAddress.ps1 b/functions/public/Set-PSIPAMAddress.ps1 new file mode 100644 index 0000000..df04f00 --- /dev/null +++ b/functions/public/Set-PSIPAMAddress.ps1 @@ -0,0 +1,172 @@ +function Set-PSIPAMAddress { + [CmdletBinding()] + param ( + [parameter( + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=0)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $Id, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Defines if address is presented as gateway", + Position=1)] + [bool] + $Gateway, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address description", + Position=2)] + [ValidateNotNullOrEmpty()] + [string] + $Description, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address hostname", + Position=3)] + [ValidateNotNullOrEmpty()] + [string] + $Hostname, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Mac address", + Position=4)] + [ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })] + [ValidateNotNullOrEmpty()] + [string] + $MAC, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Address owner", + Position=5)] + [ValidateNotNullOrEmpty()] + [string] + $Owner, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of subnet address belongs to", + Position=6)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $TagId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Controls if PTR should not be created", + Position=7)] + [bool] + $PTRIgnore, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of PowerDNS PTR record", + Position=8)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $PTRId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Note", + Position=9)] + [ValidateNotNullOrEmpty()] + [string] + $Note, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Exclude this address from status update scans (ping)", + Position=10)] + [bool] + $ExcludePing, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Id of device address belongs to", + Position=11)] + [ValidateScript({ $_ -match "^\d+$" })] + [ValidateNotNullOrEmpty()] + [string] + $DeviceId, + [parameter( + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Port", + Position=12)] + [ValidateNotNullOrEmpty()] + [string] + $Port, + + [parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] + $CustomFields + ) + process { + $_params = @{ + Controller = "addresses" + Method = "PATCH" + } + $_identifiers = @($Id) + + $_params.Add("Identifiers",$_identifiers) + + $_body = @{ } + if ($Gateway) { $_body.Add("is_gateway", [int]$Gateway) } + if ($Description) { $_body.Add("description", $Description) } + if ($Hostname) { $_body.Add("hostname", $Hostname) } + if ($MAC) { $_body.Add("mac", $MAC) } + if ($Owner) { $_body.Add("owner", $Owner) } + if ($TagId) { $_body.Add("tag", $TagId) } + if ($PTRIgnore) { $_body.Add("PTRignore", [int]$PTRIgnore) } + if ($PTRId) { $_body.add("PTR", $PTRId)} + if ($Note) { $_body.Add("note", $Note) } + if ($ExcludePing) { $_body.Add("excludePing", [int]$ExcludePing) } + if ($DeviceId) { $_body.Add("deviceId", $DeviceId) } + if ($Port) { $_body.Add("port", $Port) } + + if ($CustomFields) { + if ($CustomFields -is [PSCustomObject]) { + $_customFields = @{}; + $CustomFields | Get-Member -MemberType *Property | Where-Object { + $_customFields.($_.name) = $CustomFields.($_.name) + } + } else { $_customFields = $CustomFields } + + $_body = $_body + $_customFields + } + + $_params.Add("Params",$_body) + + try { + Invoke-PSIPAMRequest @_params + } + finally { + Get-PSIPAMAddress -Id $Id + } + } +} +Export-ModuleMember -Function Set-PSIPAMAddress \ No newline at end of file diff --git a/functions/public/Set-PSIPAMSubnet.ps1 b/functions/public/Set-PSIPAMSubnet.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d77094448e62a83a29986d229583f6ca1de193c1 GIT binary patch literal 43318 zcmeFXbx_<-^Der$yZhq0xLa^{cXxMpm*5gYaCZnEEVu^??gWS6?w5RDzxPz#`c<9t z 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