48 Commits

Author SHA1 Message Date
283fd7a703 Merge pull request 'Enhance BaseCmdlet class with additional async methods and improve documentation for cmdlet functionality' (#8) from develop into main
Some checks failed
CI/CD Pipeline / Build and Test (push) Successful in 1m8s
CI/CD Pipeline / Package Module (push) Successful in 44s
CI/CD Pipeline / Create Release (push) Failing after 15s
Reviewed-on: #8
2026-01-19 18:01:28 +03:00
3f6a59abad Enhance BaseCmdlet class with additional async methods and improve documentation for cmdlet functionality
All checks were successful
CI/CD Pipeline / Build and Test (push) Successful in 1m12s
CI/CD Pipeline / Package Module (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
CI/CD Pipeline / Build and Test (pull_request) Successful in 1m6s
CI/CD Pipeline / Package Module (pull_request) Has been skipped
CI/CD Pipeline / Create Release (pull_request) Has been skipped
2026-01-19 17:58:44 +03:00
7b9d229289 Merge pull request 'develop' (#7) from develop into main
All checks were successful
CI/CD Pipeline / Build and Test (push) Successful in 1m5s
CI/CD Pipeline / Package Module (push) Successful in 43s
CI/CD Pipeline / Create Release (push) Has been skipped
Reviewed-on: #7
2026-01-19 17:35:19 +03:00
bc86ad2965 Add IsExternalInit type, BaseCmdlet class, and SessionStatus enum
All checks were successful
CI/CD Pipeline / Build and Test (push) Successful in 1m3s
CI/CD Pipeline / Package Module (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
CI/CD Pipeline / Build and Test (pull_request) Successful in 1m7s
CI/CD Pipeline / Package Module (pull_request) Has been skipped
CI/CD Pipeline / Create Release (pull_request) Has been skipped
Introduce the IsExternalInit type for C# 9 records compatibility in .NET Standard 2.1. Implement BaseCmdlet class to provide common functionality for cmdlets, including methods for writing results and handling async operations. Add SessionStatus enum to represent API session states.
2026-01-19 17:25:41 +03:00
f56784f2aa Refactor IPAM model classes to use records for Address, Subnetwork, Vlan, Vrf, Section, Tag, Domain, Nameserver, and Session; enhance documentation and implement value equality for records. 2026-01-19 17:25:18 +03:00
694822f0d6 Update Gitea CI workflow to downgrade upload and download artifact actions from v4 to v3
All checks were successful
CI/CD Pipeline / Build and Test (push) Successful in 1m18s
CI/CD Pipeline / Package Module (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
2026-01-19 17:04:22 +03:00
df30851a3a Update Gitea CI workflow to restore class library and test project dependencies separately
Some checks failed
CI/CD Pipeline / Build and Test (push) Failing after 1m6s
CI/CD Pipeline / Package Module (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
2026-01-19 17:01:30 +03:00
88e4e12e3d Merge pull request '2026-01-19-nah9' (#6) from 2026-01-19-nah9 into main
Some checks failed
CI/CD Pipeline / Build and Test (push) Failing after 34s
CI/CD Pipeline / Package Module (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
Reviewed-on: #6
2026-01-19 16:57:01 +03:00
6541cf18c0 Update Gitea CI workflow to use updated token secret for release creation
Some checks failed
CI/CD Pipeline / Build and Test (pull_request) Failing after 1m0s
CI/CD Pipeline / Package Module (pull_request) Has been skipped
CI/CD Pipeline / Create Release (pull_request) Has been skipped
2026-01-19 16:38:05 +03:00
0b237c6d1c Enhance unit tests for Address, Domain, Nameserver, Section, Session, Subnetwork, Tag, Vlan, and Vrf models; implement mock classes for HTTP requests and cmdlet testing. 2026-01-19 14:54:55 +03:00
40e3c31c6f Remove Jenkinsfile and add unit tests for various models including Address, Domain, Nameserver, Section, Session, Subnetwork, Tag, Vlan, and Vrf. Introduce mock classes for HTTP requests and cmdlet testing. 2026-01-19 14:46:09 +03:00
114267b1d5 Added assembly name and root namespace to project file; updated session and vlan classes with new properties; modified module manifest to reference DLL and updated exported cmdlets; corrected property name in address class. 2026-01-19 14:45:31 +03:00
010cce0fd8 Refactored subnet object methods and enhanced related documentation 2026-01-19 14:44:52 +03:00
f8f240e313 Updated subnet object handling and improved documentation 2026-01-19 14:44:10 +03:00
7f856b1d09 New address in subnet by subnet object 2022-12-30 14:43:46 +03:00
31b56c75e1 jenkinsfile content replace 2022-12-27 09:39:15 +03:00
7f0d14fc25 jenkinsfile 2022-12-27 09:31:54 +03:00
00975bb1c5 jenkinsfile 2022-12-27 09:31:33 +03:00
fb39efd493 Set-Address documented 2022-12-17 23:58:22 +03:00
e45d2665fa Documentation & fixes 2022-12-17 22:51:26 +03:00
2af591dbbf New-Address documentation 2022-12-17 14:53:48 +03:00
85e13dc150 Documented new-subnet 2022-12-17 14:36:41 +03:00
d1128997cc Added pscustomobject to hashtable converter 2022-12-17 02:11:13 +03:00
bc645fc0d8 Added Close-Session cmdlet 2022-12-17 01:51:55 +03:00
ced1dde708 Merge branch 'main' of https://git.arnike.ru/Arnike/ps.ipam 2022-12-17 01:37:51 +03:00
bad183aff4 Get-Nameserver fix 2022-12-17 01:37:50 +03:00
e456d7affe Remove-Address fix 2022-12-17 01:35:33 +03:00
Arnike
4c128f5864 Get-Subnet fix 2022-12-17 01:20:34 +03:00
Arnike
0913fc2069 content-type fix 2022-12-17 01:11:05 +03:00
ea9b760933 nameserver type fix. 2022-12-14 16:55:08 +03:00
833bb183eb Encoding fix 2022-12-14 16:00:00 +03:00
ed9d648074 Merge pull request 'Types-implement' (#5) from Types-implement into main
Reviewed-on: #5
2022-12-14 12:54:08 +00:00
ad9744bf56 version fix 2022-12-14 15:53:43 +03:00
1291bc0b69 CI\CD prep 2022-12-14 15:40:31 +03:00
b1c5feb73b Added nameserver type 2022-12-14 15:27:32 +03:00
6fb2e81142 Fixed subcontrollers 2022-12-14 15:26:43 +03:00
9912656752 Documentation 2022-12-14 13:22:13 +03:00
538d84305f subnetwork type fix 2022-12-14 11:23:27 +03:00
078beb8d3c Merge pull request 'Types-implement' (#4) from Types-implement into main
Reviewed-on: #4
2022-12-13 20:55:44 +00:00
9069989af3 Fixes 2022-12-12 18:58:13 +03:00
0742737f0d Added Assign-Tag cmdlet 2022-12-12 18:57:51 +03:00
5a34f03779 Fixes, added session class 2022-12-12 13:08:04 +03:00
7d0d1b53ae first working types 2022-12-06 17:20:45 +03:00
a1b03b81c0 replaced hardcoded text by enums in some functions 2022-12-05 17:48:20 +03:00
4fc6bac4b5 subnet type 2022-12-05 16:51:33 +03:00
c4b15d7387 enums 2022-12-05 15:29:03 +03:00
affbd18de2 classlib all types implemented 2022-12-05 14:08:59 +03:00
09cd00dc66 Base 2022-12-05 08:48:37 +03:00
99 changed files with 7278 additions and 720 deletions

124
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,124 @@
name: CI/CD Pipeline
on:
push:
branches:
- main
- master
- develop
tags:
- 'v*'
pull_request:
branches:
- main
- master
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore class library
run: dotnet restore classlib/classlib.csproj
- name: Restore test project
run: dotnet restore classlib.tests/classlib.tests.csproj
- name: Build class library
run: dotnet build classlib/classlib.csproj --configuration Release --no-restore
- name: Build test project
run: dotnet build classlib.tests/classlib.tests.csproj --configuration Release --no-restore
- name: Run tests
run: dotnet test classlib.tests/classlib.tests.csproj --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage"
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: classlib.tests/TestResults/
retention-days: 30
package:
name: Package Module
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build Release
run: dotnet build classlib/classlib.csproj --configuration Release
- name: Create module package
run: |
mkdir -p output/ps.ipam
# Copy compiled DLL
cp classlib/bin/Release/netstandard2.1/ps.ipam.dll output/ps.ipam/
# Copy module manifest and related files
cp ps.ipam.psd1 output/ps.ipam/
cp ps.ipam.psm1 output/ps.ipam/
cp LICENSE output/ps.ipam/
cp README.md output/ps.ipam/
# Copy types directory
cp -r types output/ps.ipam/
# Copy functions directory
cp -r functions output/ps.ipam/
# Copy images directory
cp -r images output/ps.ipam/
- name: Upload module artifact
uses: actions/upload-artifact@v3
with:
name: ps.ipam-module
path: output/ps.ipam/
retention-days: 90
release:
name: Create Release
runs-on: ubuntu-latest
needs: package
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download module artifact
uses: actions/download-artifact@v3
with:
name: ps.ipam-module
path: ps.ipam
- name: Create release archive
run: |
zip -r ps.ipam-${{ github.ref_name }}.zip ps.ipam/
tar -czvf ps.ipam-${{ github.ref_name }}.tar.gz ps.ipam/
- name: Create Gitea Release
uses: actions/gitea-release-action@v1
with:
token: ${{ secrets.TOKEN }}
files: |
ps.ipam-${{ github.ref_name }}.zip
ps.ipam-${{ github.ref_name }}.tar.gz
title: Release ${{ github.ref_name }}
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.dll
bin
obj
.code-workspace

View File

@@ -27,6 +27,7 @@
[![License](https://img.shields.io/badge/license-GPL2+-blue.svg)](https://git.arnike.ru/Arnike/ps.ipam/src/branch/main/LICENSE) [![License](https://img.shields.io/badge/license-GPL2+-blue.svg)](https://git.arnike.ru/Arnike/ps.ipam/src/branch/main/LICENSE)
![Powershell](https://img.shields.io/badge/powershell-v5.1+-blue.svg) ![Powershell](https://img.shields.io/badge/powershell-v5.1+-blue.svg)
![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/ps.ipam)
<h3 align="center">PS.IPAM</h3> <h3 align="center">PS.IPAM</h3>

View File

@@ -0,0 +1,330 @@
namespace PS.IPAM.Tests.Cmdlets;
using System.Net;
using FluentAssertions;
using PS.IPAM;
using PS.IPAM.Cmdlets;
using PS.IPAM.Helpers;
using PS.IPAM.Tests.Mocks;
using Xunit;
/// <summary>
/// Tests for the GetAddressCmdlet.
/// Note: Full cmdlet testing with parameter sets requires a PowerShell runspace.
/// These tests focus on verifying the cmdlet structure and the underlying RequestHelper functionality.
/// </summary>
[Collection("Sequential")]
public class GetAddressCmdletTests : IDisposable
{
private MockHttpMessageHandler? _mockHandler;
public GetAddressCmdletTests()
{
// Clean state before each test
SessionManager.CloseSession();
RequestHelper.TestHttpHandler = null;
}
public void Dispose()
{
// Clean up after each test
SessionManager.CloseSession();
RequestHelper.TestHttpHandler = null;
_mockHandler?.ForceDispose();
}
private void SetupSession()
{
SessionManager.CreateSessionWithToken("https://ipam.example.com", "testapp", "test-token");
}
private MockHttpMessageHandler SetupMockHandler()
{
_mockHandler = new MockHttpMessageHandler();
RequestHelper.TestHttpHandler = _mockHandler;
return _mockHandler;
}
[Fact]
public void GetAddressCmdlet_Exists()
{
// Verify the cmdlet class exists and can be instantiated
var cmdlet = new GetAddressCmdlet();
cmdlet.Should().NotBeNull();
}
[Fact]
public void GetAddressCmdlet_IdProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.Id = 42;
cmdlet.Id.Should().Be(42);
}
[Fact]
public void GetAddressCmdlet_IPProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.IP = IPAddress.Parse("192.168.1.100");
cmdlet.IP.Should().Be(IPAddress.Parse("192.168.1.100"));
}
[Fact]
public void GetAddressCmdlet_HostNameProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.HostName = "server01.example.com";
cmdlet.HostName.Should().Be("server01.example.com");
}
[Fact]
public void GetAddressCmdlet_HostBaseProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.HostBase = "server";
cmdlet.HostBase.Should().Be("server");
}
[Fact]
public void GetAddressCmdlet_TagIdProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.TagId = 2;
cmdlet.TagId.Should().Be(2);
}
[Fact]
public void GetAddressCmdlet_SubnetIdProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.SubnetId = 10;
cmdlet.SubnetId.Should().Be(10);
}
[Fact]
public void GetAddressCmdlet_SubnetCIDRProperty_Exists()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.SubnetCIDR = "192.168.1.0/24";
cmdlet.SubnetCIDR.Should().Be("192.168.1.0/24");
}
[Fact]
public void GetAddressCmdlet_InheritsFromBaseCmdlet()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.Should().BeAssignableTo<BaseCmdlet>();
}
// Test the underlying RequestHelper functionality that the cmdlet uses
[Fact]
public async Task RequestHelper_GetAddressById_ReturnsAddress()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var addressJson = @"{
""id"": 1,
""subnetId"": 10,
""ip"": ""192.168.1.100"",
""hostname"": ""server01"",
""description"": ""Test server""
}";
handler.WithSuccessResponse(addressJson);
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "1" }
);
// Assert
result.Should().BeOfType<Address>();
var address = (Address)result!;
address.Id.Should().Be(1);
address.Ip.Should().Be("192.168.1.100");
address.Hostname.Should().Be("server01");
}
[Fact]
public async Task RequestHelper_GetAddressById_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"{""id"": 42, ""ip"": ""10.0.0.1""}");
// Act
await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "42" }
);
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/addresses/42/");
}
[Fact]
public async Task RequestHelper_SearchByIP_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"{""id"": 1, ""ip"": ""192.168.1.50""}");
// Act
await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "search", "192.168.1.50" }
);
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/addresses/search/192.168.1.50/");
}
[Fact]
public async Task RequestHelper_SearchByHostname_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"{""id"": 1, ""ip"": ""10.0.0.5"", ""hostname"": ""myserver.example.com""}");
// Act
await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "search_hostname", "myserver.example.com" }
);
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/addresses/search_hostname/myserver.example.com/");
}
[Fact]
public async Task RequestHelper_GetSubnetAddresses_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"[{""id"": 1, ""ip"": ""192.168.1.1""}]");
// Act
await RequestHelper.InvokeRequest(
"GET",
ApiController.Subnets,
ModelType.Address,
null,
null,
new[] { "10", "addresses" }
);
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/subnets/10/addresses/");
}
[Fact]
public async Task RequestHelper_GetAddressesByTag_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"[{""id"": 1, ""ip"": ""10.0.0.1"", ""tag"": 2}]");
// Act
await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "tags", "2", "addresses" }
);
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/addresses/tags/2/addresses/");
}
[Fact]
public async Task RequestHelper_ReturnsMultipleAddresses()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"[
{""id"": 1, ""ip"": ""192.168.1.1""},
{""id"": 2, ""ip"": ""192.168.1.2""},
{""id"": 3, ""ip"": ""192.168.1.3""}
]");
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
ApiController.Subnets,
ModelType.Address,
null,
null,
new[] { "10", "addresses" }
);
// Assert
result.Should().BeAssignableTo<System.Collections.IEnumerable>();
var addresses = ((System.Collections.IEnumerable)result!).Cast<object>().ToList();
addresses.Should().HaveCount(3);
addresses.Should().AllBeOfType<Address>();
}
[Fact]
public async Task RequestHelper_With404Response_ReturnsNull()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithNotFoundResponse();
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "999" }
);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task RequestHelper_WithNoSession_ThrowsException()
{
// Arrange - no session set up
// Act
var action = async () => await RequestHelper.InvokeRequest(
"GET",
ApiController.Addresses,
ModelType.Address
);
// Assert
await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("No session available!");
}
}

View File

@@ -0,0 +1,110 @@
namespace PS.IPAM.Tests.Cmdlets;
using FluentAssertions;
using PS.IPAM;
using PS.IPAM.Cmdlets;
using PS.IPAM.Helpers;
using Xunit;
/// <summary>
/// Tests for the NewSessionCmdlet.
/// Note: Full cmdlet testing with parameter sets requires a PowerShell runspace.
/// These tests focus on verifying the cmdlet structure and basic functionality.
/// </summary>
[Collection("Sequential")]
public class NewSessionCmdletTests : IDisposable
{
public NewSessionCmdletTests()
{
// Clean state before each test
SessionManager.CloseSession();
}
public void Dispose()
{
// Clean up after each test
SessionManager.CloseSession();
}
[Fact]
public void NewSessionCmdlet_Exists()
{
// Verify the cmdlet class exists and can be instantiated
var cmdlet = new NewSessionCmdlet();
cmdlet.Should().NotBeNull();
}
[Fact]
public void NewSessionCmdlet_URLProperty_Exists()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.URL = "https://ipam.example.com";
cmdlet.URL.Should().Be("https://ipam.example.com");
}
[Fact]
public void NewSessionCmdlet_AppIDProperty_Exists()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.AppID = "testapp";
cmdlet.AppID.Should().Be("testapp");
}
[Fact]
public void NewSessionCmdlet_TokenProperty_Exists()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.Token = "my-api-token";
cmdlet.Token.Should().Be("my-api-token");
}
[Fact]
public void NewSessionCmdlet_CredentialsProperty_Exists()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.Credentials = null;
cmdlet.Credentials.Should().BeNull();
}
[Fact]
public void NewSessionCmdlet_IgnoreSSLProperty_Exists()
{
var cmdlet = new NewSessionCmdlet();
// SwitchParameter defaults to false
cmdlet.IgnoreSSL.IsPresent.Should().BeFalse();
// Setting it to true
var switchParam = new System.Management.Automation.SwitchParameter(true);
cmdlet.IgnoreSSL = switchParam;
// Note: SwitchParameter is a struct, so getting the value back may not work as expected
// Just verify the property exists and can be set
}
[Fact]
public void SessionManager_CreateSessionWithToken_WorksCorrectly()
{
// This tests the underlying functionality that the cmdlet uses
var session = SessionManager.CreateSessionWithToken(
"https://ipam.example.com",
"testapp",
"my-api-token"
);
session.Should().NotBeNull();
session.URL.Should().Be("https://ipam.example.com");
session.AppID.Should().Be("testapp");
session.Token.Should().Be("my-api-token");
session.AuthType.Should().Be(AuthType.Token);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
// Verify it was set as current session
SessionManager.CurrentSession.Should().BeSameAs(session);
}
[Fact]
public void NewSessionCmdlet_InheritsFromBaseCmdlet()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.Should().BeAssignableTo<BaseCmdlet>();
}
}

View File

@@ -0,0 +1,503 @@
namespace PS.IPAM.Tests.Helpers;
using System.Net;
using FluentAssertions;
using PS.IPAM;
using PS.IPAM.Helpers;
using PS.IPAM.Tests.Mocks;
using Xunit;
[Collection("Sequential")]
public class RequestHelperTests : IDisposable
{
private MockHttpMessageHandler? _mockHandler;
public RequestHelperTests()
{
// Clean state before each test
SessionManager.CloseSession();
RequestHelper.TestHttpHandler = null;
}
public void Dispose()
{
// Clean up after each test
SessionManager.CloseSession();
RequestHelper.TestHttpHandler = null;
_mockHandler?.ForceDispose();
}
private void SetupSession(AuthType authType = AuthType.Token)
{
if (authType == AuthType.Token)
{
SessionManager.CreateSessionWithToken("https://ipam.example.com", "testapp", "test-token");
}
else
{
// For credentials auth, we need to set up a session manually with future expiry
var session = new Session(
AuthType.Credentials,
"cred-token",
"testapp",
"https://ipam.example.com",
DateTime.Now.AddHours(1),
null
);
SessionManager.CurrentSession = session;
}
}
private MockHttpMessageHandler SetupMockHandler()
{
_mockHandler = new MockHttpMessageHandler();
RequestHelper.TestHttpHandler = _mockHandler;
return _mockHandler;
}
[Fact]
public async Task InvokeRequest_WithNoSession_ThrowsException()
{
// Arrange - no session set up
// Act
var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("No session available!");
}
[Fact]
public async Task InvokeRequest_WithValidSession_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{\"id\":1,\"ip\":\"192.168.1.1\"}");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
handler.LastRequest.Should().NotBeNull();
handler.LastRequest!.RequestUri!.ToString().Should().Be("https://ipam.example.com/api/testapp/addresses/1/");
}
[Fact]
public async Task InvokeRequest_WithSubController_BuildsCorrectUri()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("[]");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Subnets, null, ApiSubController.Tags, null, new[] { "1" });
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/subnets/tags/1/");
}
[Fact]
public async Task InvokeRequest_WithTokenAuth_AddsPhpipamTokenHeader()
{
// Arrange
SetupSession(AuthType.Token);
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.GetLastRequestHeader("phpipam-token").Should().Be("test-token");
}
[Fact]
public async Task InvokeRequest_WithCredentialsAuth_AddsTokenHeader()
{
// Arrange
SetupSession(AuthType.Credentials);
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.GetLastRequestHeader("token").Should().Be("cred-token");
}
[Fact]
public async Task InvokeRequest_GetMethod_UsesHttpGet()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Get);
}
[Fact]
public async Task InvokeRequest_PostMethod_UsesHttpPost()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{\"id\":1}");
// Act
await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, new { ip = "10.0.0.1" });
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Post);
}
[Fact]
public async Task InvokeRequest_PatchMethod_UsesHttpPatch()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{\"id\":1}");
// Act
await RequestHelper.InvokeRequest("PATCH", ApiController.Addresses, null, null, new { description = "updated" }, new[] { "1" });
// Assert
handler.LastRequest!.Method.Should().Be(new HttpMethod("PATCH"));
}
[Fact]
public async Task InvokeRequest_DeleteMethod_UsesHttpDelete()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("DELETE", ApiController.Addresses, null, null, null, new[] { "1" });
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Delete);
}
[Fact]
public async Task InvokeRequest_With404Response_ReturnsNull()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithNotFoundResponse();
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "999" });
// Assert
result.Should().BeNull();
}
[Fact]
public async Task InvokeRequest_WithErrorResponse_ThrowsHttpRequestException()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithErrorResponse(HttpStatusCode.InternalServerError, "Server error");
// Act
var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
await action.Should().ThrowAsync<HttpRequestException>();
}
[Fact]
public async Task InvokeRequest_WithAddressType_ReturnsAddressObject()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var addressJson = @"{
""id"": 1,
""subnetId"": 10,
""ip"": ""192.168.1.100"",
""is_gateway"": false,
""description"": ""Test server"",
""hostname"": ""server01"",
""mac"": ""00:11:22:33:44:55"",
""owner"": ""admin"",
""tag"": 2,
""deviceId"": 0,
""location"": """",
""port"": """",
""note"": """",
""lastSeen"": null,
""excludePing"": false,
""PTRignore"": false,
""PTR"": 0,
""firewallAddressObject"": """",
""editDate"": null,
""customer_id"": 0
}";
handler.WithSuccessResponse(addressJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Address>();
var address = (Address)result!;
address.Id.Should().Be(1);
address.Ip.Should().Be("192.168.1.100");
address.Hostname.Should().Be("server01");
}
[Fact]
public async Task InvokeRequest_WithAddressArray_ReturnsListOfAddresses()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var addressArrayJson = @"[
{""id"": 1, ""subnetId"": 10, ""ip"": ""192.168.1.1"", ""hostname"": ""host1""},
{""id"": 2, ""subnetId"": 10, ""ip"": ""192.168.1.2"", ""hostname"": ""host2""}
]";
handler.WithSuccessResponse(addressArrayJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Address, null, null, new[] { "10", "addresses" });
// Assert
result.Should().BeAssignableTo<IEnumerable<object>>();
var addresses = ((IEnumerable<object>)result!).ToList();
addresses.Should().HaveCount(2);
addresses[0].Should().BeOfType<Address>();
((Address)addresses[0]).Ip.Should().Be("192.168.1.1");
}
[Fact]
public async Task InvokeRequest_WithVlanType_ReturnsVlanObject()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var vlanJson = @"{
""vlanId"": 100,
""domainId"": 1,
""name"": ""Production"",
""number"": 100,
""description"": ""Production VLAN"",
""editDate"": null,
""customer_id"": 0
}";
handler.WithSuccessResponse(vlanJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Vlan, ModelType.Vlan, null, null, new[] { "100" });
// Assert
result.Should().BeOfType<Vlan>();
var vlan = (Vlan)result!;
vlan.Name.Should().Be("Production");
vlan.Number.Should().Be(100);
}
[Fact]
public async Task InvokeRequest_WithSubnetworkType_ReturnsSubnetworkObject()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var subnetJson = @"{
""id"": 1,
""subnet"": ""192.168.1.0"",
""mask"": 24,
""sectionId"": 1,
""description"": ""Test subnet"",
""linked_subnet"": """",
""firewallAddressObject"": """",
""vrfId"": 0,
""masterSubnetId"": 0,
""allowRequests"": false,
""vlanId"": 100,
""showName"": false,
""deviceId"": 0,
""permissions"": """",
""pingSubnet"": false,
""discoverSubnet"": false,
""resolveDNS"": false,
""DNSrecursive"": false,
""DNSrecords"": false,
""nameserverId"": 0,
""scanAgent"": false,
""isFolder"": false,
""isFull"": false,
""isPool"": false,
""state"": 1,
""threshold"": 0,
""location"": 0,
""editDate"": null,
""lastScan"": null,
""lastDiscovery"": null,
""calculation"": {}
}";
handler.WithSuccessResponse(subnetJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Subnetwork, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Subnetwork>();
var subnet = (Subnetwork)result!;
subnet.Subnet.Should().Be("192.168.1.0");
subnet.Mask.Should().Be(24);
subnet.CIDR.Should().Be("192.168.1.0/24");
}
[Fact]
public async Task InvokeRequest_WithSectionType_ReturnsSectionObject()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var sectionJson = @"{
""id"": 1,
""name"": ""Production"",
""description"": ""Production section"",
""masterSection"": 0,
""permissions"": """",
""strictMode"": false,
""subnetOrdering"": ""default"",
""order"": 1,
""editDate"": null,
""showSubnet"": true,
""showVlan"": true,
""showVRF"": false,
""showSupernetOnly"": false,
""DNS"": 0
}";
handler.WithSuccessResponse(sectionJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Sections, ModelType.Section, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Section>();
var section = (Section)result!;
section.Name.Should().Be("Production");
}
[Fact]
public async Task InvokeRequest_WithCustomFields_ParsesExtendedData()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
var addressJson = @"{
""id"": 1,
""subnetId"": 10,
""ip"": ""192.168.1.100"",
""hostname"": ""server01"",
""custom_environment"": ""production"",
""custom_owner"": ""team-a""
}";
handler.WithSuccessResponse(addressJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
var address = (Address)result!;
address.ExtendedData.Should().NotBeNull();
var extendedData = address.ExtendedData!;
extendedData.Should().ContainKey("custom_environment");
extendedData.Should().ContainKey("custom_owner");
}
[Fact]
public async Task InvokeRequest_WithNoType_ReturnsDynamicData()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"{""some"": ""data""}");
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Tools);
// Assert
result.Should().NotBeNull();
}
[Fact]
public async Task InvokeRequest_PostWithParameters_SerializesJsonBody()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse(@"{""id"": 1}");
var parameters = new { ip = "10.0.0.1", subnetId = 5, description = "New address" };
// Act
await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, parameters);
// Assert
handler.LastRequest!.Content.Should().NotBeNull();
var content = await handler.LastRequest.Content!.ReadAsStringAsync();
content.Should().Contain("10.0.0.1");
content.Should().Contain("subnetId");
}
[Fact]
public async Task InvokeRequest_SetsAcceptJsonHeader()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.LastRequest!.Headers.Accept.Should().Contain(h => h.MediaType == "application/json");
}
[Fact]
public async Task InvokeRequest_WithEmptyResponse_ReturnsNull()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithResponse(HttpStatusCode.OK, "", "application/json");
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task InvokeRequest_WithNullDataInResponse_ReturnsNull()
{
// Arrange
SetupSession();
var handler = SetupMockHandler();
handler.WithJsonResponse(@"{""code"": 200, ""success"": true, ""data"": null}");
// Act
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address);
// Assert
result.Should().BeNull();
}
}

View File

@@ -0,0 +1,193 @@
namespace PS.IPAM.Tests.Helpers;
using FluentAssertions;
using PS.IPAM;
using PS.IPAM.Helpers;
using Xunit;
[Collection("Sequential")]
public class SessionManagerTests : IDisposable
{
public SessionManagerTests()
{
// Ensure clean state before each test
SessionManager.CloseSession();
}
public void Dispose()
{
// Clean up after each test
SessionManager.CloseSession();
}
[Fact]
public void GetSessionStatus_WhenNoSession_ReturnsNoSession()
{
// Arrange
SessionManager.CurrentSession = null;
// Act
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be(SessionStatus.NoSession);
}
[Fact]
public void GetSessionStatus_WhenSessionWithNullExpires_ReturnsValid()
{
// Arrange
var session = new Session(AuthType.Token, "test-token", "app", "https://test.com", null, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be(SessionStatus.Valid);
}
[Fact]
public void GetSessionStatus_WhenSessionNotExpired_ReturnsValid()
{
// Arrange
var futureExpiry = DateTime.Now.AddHours(1);
var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", futureExpiry, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be(SessionStatus.Valid);
}
[Fact]
public void GetSessionStatus_WhenSessionExpired_ReturnsExpired()
{
// Arrange
var pastExpiry = DateTime.Now.AddHours(-1);
var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", pastExpiry, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be(SessionStatus.Expired);
}
[Fact]
public void CreateSessionWithToken_CreatesValidSession()
{
// Arrange
var url = "https://ipam.example.com";
var appId = "myApp";
var token = "static-api-token";
// Act
var session = SessionManager.CreateSessionWithToken(url, appId, token);
// Assert
session.Should().NotBeNull();
session.URL.Should().Be(url);
session.AppID.Should().Be(appId);
session.Token.Should().Be(token);
session.AuthType.Should().Be(AuthType.Token);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
}
[Fact]
public void CreateSessionWithToken_SetsCurrentSession()
{
// Arrange
var url = "https://ipam.example.com";
var appId = "myApp";
var token = "api-token";
// Act
var session = SessionManager.CreateSessionWithToken(url, appId, token);
// Assert
SessionManager.CurrentSession.Should().BeSameAs(session);
}
[Fact]
public void CloseSession_ClearsCurrentSession()
{
// Arrange
SessionManager.CreateSessionWithToken("https://test.com", "app", "token");
SessionManager.CurrentSession.Should().NotBeNull();
// Act
SessionManager.CloseSession();
// Assert
SessionManager.CurrentSession.Should().BeNull();
}
[Fact]
public void CloseSession_WhenNoSession_DoesNotThrow()
{
// Arrange
SessionManager.CurrentSession = null;
// Act
var action = () => SessionManager.CloseSession();
// Assert
action.Should().NotThrow();
}
[Fact]
public void CurrentSession_CanBeSetDirectly()
{
// Arrange
var session = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
// Act
SessionManager.CurrentSession = session;
// Assert
SessionManager.CurrentSession.Should().BeSameAs(session);
}
[Fact]
public void CreateHttpClient_WithoutIgnoreSsl_ReturnsHttpClient()
{
// Act
using var client = SessionManager.CreateHttpClient(false);
// Assert
client.Should().NotBeNull();
client.Should().BeOfType<HttpClient>();
}
[Fact]
public void CreateHttpClient_WithIgnoreSsl_ReturnsHttpClient()
{
// Act
using var client = SessionManager.CreateHttpClient(true);
// Assert
client.Should().NotBeNull();
client.Should().BeOfType<HttpClient>();
}
[Fact]
public void CreateSessionWithToken_ReplacesExistingSession()
{
// Arrange
SessionManager.CreateSessionWithToken("https://old.com", "oldApp", "oldToken");
// Act
var newSession = SessionManager.CreateSessionWithToken("https://new.com", "newApp", "newToken");
// Assert
SessionManager.CurrentSession.Should().BeSameAs(newSession);
SessionManager.CurrentSession!.URL.Should().Be("https://new.com");
SessionManager.CurrentSession.AppID.Should().Be("newApp");
SessionManager.CurrentSession.Token.Should().Be("newToken");
}
}

View File

@@ -0,0 +1,139 @@
namespace PS.IPAM.Tests.Mocks;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Reflection;
/// <summary>
/// Helper class for testing PowerShell cmdlets.
/// </summary>
public class CmdletTestHelper<T> : IDisposable where T : PSCmdlet, new()
{
private readonly T _cmdlet;
private readonly List<object> _output = new();
private readonly List<ErrorRecord> _errors = new();
private bool _disposed;
public CmdletTestHelper()
{
_cmdlet = new T();
}
/// <summary>
/// Gets the cmdlet instance for setting parameters.
/// </summary>
public T Cmdlet => _cmdlet;
/// <summary>
/// Gets the output objects written by the cmdlet.
/// </summary>
public IReadOnlyList<object> Output => _output.AsReadOnly();
/// <summary>
/// Gets the errors written by the cmdlet.
/// </summary>
public IReadOnlyList<ErrorRecord> Errors => _errors.AsReadOnly();
/// <summary>
/// Gets whether any errors were written.
/// </summary>
public bool HasErrors => _errors.Count > 0;
/// <summary>
/// Invokes the cmdlet and captures output.
/// </summary>
public void Invoke()
{
// Use reflection to call the protected ProcessRecord method
var processMethod = typeof(T).GetMethod("ProcessRecord",
BindingFlags.NonPublic | BindingFlags.Instance);
if (processMethod == null)
{
throw new InvalidOperationException("ProcessRecord method not found on cmdlet.");
}
// Set up a mock command runtime to capture output
var runtime = new MockCommandRuntime(_output, _errors);
var runtimeProperty = typeof(Cmdlet).GetProperty("CommandRuntime",
BindingFlags.Public | BindingFlags.Instance);
runtimeProperty?.SetValue(_cmdlet, runtime);
// Invoke the method
try
{
processMethod.Invoke(_cmdlet, null);
}
catch (TargetInvocationException ex) when (ex.InnerException != null)
{
// Unwrap the exception
throw ex.InnerException;
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
}
}
}
/// <summary>
/// Mock implementation of ICommandRuntime for capturing cmdlet output.
/// This is a minimal implementation that only handles the methods we need for testing.
/// </summary>
internal class MockCommandRuntime : ICommandRuntime
{
private readonly List<object> _output;
private readonly List<ErrorRecord> _errors;
public MockCommandRuntime(List<object> output, List<ErrorRecord> errors)
{
_output = output;
_errors = errors;
}
public PSHost Host => null!;
public PSTransactionContext CurrentPSTransaction => null!;
public bool ShouldContinue(string query, string caption) => true;
public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) => true;
public bool ShouldProcess(string target) => true;
public bool ShouldProcess(string target, string action) => true;
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) => true;
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason)
{
shouldProcessReason = ShouldProcessReason.None;
return true;
}
public bool TransactionAvailable() => false;
public void ThrowTerminatingError(ErrorRecord errorRecord) => throw errorRecord.Exception;
public void WriteCommandDetail(string text) { }
public void WriteDebug(string text) { }
public void WriteError(ErrorRecord errorRecord) => _errors.Add(errorRecord);
public void WriteObject(object sendToPipeline) => _output.Add(sendToPipeline);
public void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (enumerateCollection && sendToPipeline is System.Collections.IEnumerable enumerable)
{
foreach (var item in enumerable)
{
_output.Add(item);
}
}
else
{
_output.Add(sendToPipeline);
}
}
public void WriteProgress(ProgressRecord progressRecord) { }
public void WriteProgress(long sourceId, ProgressRecord progressRecord) { }
public void WriteVerbose(string text) { }
public void WriteWarning(string text) { }
}

View File

@@ -0,0 +1,172 @@
namespace PS.IPAM.Tests.Mocks;
using System.Net;
/// <summary>
/// A mock HTTP message handler for testing HTTP requests without making actual network calls.
/// This handler does not dispose itself when the HttpClient is disposed, allowing reuse in tests.
/// </summary>
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly Queue<MockResponse> _responses = new();
private readonly List<HttpRequestMessage> _requests = new();
private bool _disposed = false;
/// <summary>
/// Gets all requests that were sent through this handler.
/// </summary>
public IReadOnlyList<HttpRequestMessage> Requests => _requests.AsReadOnly();
/// <summary>
/// Gets the last request that was sent through this handler.
/// </summary>
public HttpRequestMessage? LastRequest => _requests.LastOrDefault();
/// <summary>
/// Queues a response to be returned for the next request.
/// </summary>
public MockHttpMessageHandler WithResponse(HttpStatusCode statusCode, string content, string contentType = "application/json")
{
_responses.Enqueue(new MockResponse(statusCode, content, contentType));
return this;
}
/// <summary>
/// Queues a successful JSON response.
/// </summary>
public MockHttpMessageHandler WithJsonResponse(string jsonContent)
{
return WithResponse(HttpStatusCode.OK, jsonContent, "application/json");
}
/// <summary>
/// Queues a successful response with phpIPAM-style wrapper.
/// </summary>
public MockHttpMessageHandler WithSuccessResponse(string dataJson)
{
var response = $"{{\"code\":200,\"success\":true,\"data\":{dataJson}}}";
return WithJsonResponse(response);
}
/// <summary>
/// Queues a 404 Not Found response.
/// </summary>
public MockHttpMessageHandler WithNotFoundResponse()
{
return WithResponse(HttpStatusCode.NotFound, "{\"code\":404,\"success\":false,\"message\":\"Not found\"}", "application/json");
}
/// <summary>
/// Queues an error response.
/// </summary>
public MockHttpMessageHandler WithErrorResponse(HttpStatusCode statusCode, string message)
{
var response = $"{{\"code\":{(int)statusCode},\"success\":false,\"message\":\"{message}\"}}";
return WithResponse(statusCode, response, "application/json");
}
/// <summary>
/// Queues an exception to be thrown on the next request.
/// </summary>
public MockHttpMessageHandler WithException(Exception exception)
{
_responses.Enqueue(new MockResponse(exception));
return this;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MockHttpMessageHandler));
}
_requests.Add(request);
if (_responses.Count == 0)
{
throw new InvalidOperationException("No mock response configured. Call WithResponse() before making requests.");
}
var mockResponse = _responses.Dequeue();
if (mockResponse.Exception != null)
{
throw mockResponse.Exception;
}
var response = new HttpResponseMessage(mockResponse.StatusCode)
{
Content = new StringContent(mockResponse.Content, System.Text.Encoding.UTF8, mockResponse.ContentType),
RequestMessage = request
};
return Task.FromResult(response);
}
protected override void Dispose(bool disposing)
{
// Don't actually dispose - allow reuse in tests
// The test itself is responsible for cleanup
}
/// <summary>
/// Actually disposes the handler. Call this in test cleanup.
/// </summary>
public void ForceDispose()
{
_disposed = true;
base.Dispose(true);
}
/// <summary>
/// Verifies that a request was made to the expected URL.
/// </summary>
public bool WasRequestMadeTo(string urlContains)
{
return _requests.Any(r => r.RequestUri?.ToString().Contains(urlContains) == true);
}
/// <summary>
/// Verifies that a request with the expected method was made.
/// </summary>
public bool WasRequestMadeWithMethod(HttpMethod method)
{
return _requests.Any(r => r.Method == method);
}
/// <summary>
/// Gets the value of a header from the last request.
/// </summary>
public string? GetLastRequestHeader(string headerName)
{
if (LastRequest?.Headers.TryGetValues(headerName, out var values) == true)
{
return values.FirstOrDefault();
}
return null;
}
private class MockResponse
{
public HttpStatusCode StatusCode { get; }
public string Content { get; }
public string ContentType { get; }
public Exception? Exception { get; }
public MockResponse(HttpStatusCode statusCode, string content, string contentType)
{
StatusCode = statusCode;
Content = content;
ContentType = contentType;
}
public MockResponse(Exception exception)
{
Exception = exception;
StatusCode = HttpStatusCode.InternalServerError;
Content = string.Empty;
ContentType = string.Empty;
}
}
}

View File

@@ -0,0 +1,136 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class AddressTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var subnetId = 10;
var ip = "192.168.1.100";
var isGateway = true;
var description = "Test server";
var hostname = "server01.example.com";
var mac = "00:11:22:33:44:55";
var owner = "admin";
var tagId = 2;
var deviceId = 5;
var location = "DC1";
var port = "eth0";
var note = "Production server";
var lastSeen = new DateTime(2026, 1, 15, 10, 30, 0);
var excludePing = false;
var ptrIgnore = true;
var ptr = 1;
var firewallObject = "FW_SERVER01";
var editDate = new DateTime(2026, 1, 10, 8, 0, 0);
var customerId = 100;
var extendedData = new Dictionary<string, object> { { "custom_field1", "value1" } };
// Act
var address = new Address(
id, subnetId, ip, isGateway, description, hostname, mac, owner,
tagId, deviceId, location, port, note, lastSeen, excludePing,
ptrIgnore, ptr, firewallObject, editDate, customerId, extendedData
);
// Assert
address.Id.Should().Be(id);
address.SubnetId.Should().Be(subnetId);
address.Ip.Should().Be(ip);
address.IsGateway.Should().Be(isGateway);
address.Description.Should().Be(description);
address.Hostname.Should().Be(hostname);
address.MAC.Should().Be(mac);
address.Owner.Should().Be(owner);
address.TagId.Should().Be(tagId);
address.DeviceId.Should().Be(deviceId);
address.Location.Should().Be(location);
address.Port.Should().Be(port);
address.Note.Should().Be(note);
address.LastSeen.Should().Be(lastSeen);
address.ExcludePing.Should().Be(excludePing);
address.PTRIgnore.Should().Be(ptrIgnore);
address.PTR.Should().Be(ptr);
address.FirewallAddressObject.Should().Be(firewallObject);
address.EditDate.Should().Be(editDate);
address.CustomerId.Should().Be(customerId);
address.ExtendedData.Should().BeEquivalentTo(extendedData);
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var address = new Address(
1, 10, "10.0.0.1", false, "", "", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Assert
address.LastSeen.Should().BeNull();
address.EditDate.Should().BeNull();
address.ExtendedData.Should().BeNull();
}
[Fact]
public void ToString_ReturnsIpAddress()
{
// Arrange
var address = new Address(
1, 10, "192.168.1.50", false, "Test", "host.local", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Act
var result = address.ToString();
// Assert
result.Should().Be("192.168.1.50");
}
[Theory]
[InlineData("10.0.0.1")]
[InlineData("172.16.0.100")]
[InlineData("192.168.255.255")]
[InlineData("2001:db8::1")]
public void ToString_ReturnsCorrectIp_ForVariousAddresses(string expectedIp)
{
// Arrange
var address = new Address(
1, 1, expectedIp, false, "", "", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Act & Assert
address.ToString().Should().Be(expectedIp);
}
[Fact]
public void Record_Equality_WorksCorrectly()
{
// Arrange
var address1 = new Address(
1, 10, "192.168.1.1", false, "Test", "host", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
var address2 = new Address(
1, 10, "192.168.1.1", false, "Test", "host", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Assert
address1.Should().Be(address2);
}
}

View File

@@ -0,0 +1,43 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class DomainTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Default";
var description = "Default L2 domain";
var sections = "1;2;3";
// Act
var domain = new Domain(id, name, description, sections);
// Assert
domain.Id.Should().Be(id);
domain.Name.Should().Be(name);
domain.Description.Should().Be(description);
domain.Sections.Should().Be(sections);
}
[Theory]
[InlineData("Default")]
[InlineData("Datacenter1")]
[InlineData("Branch_Office")]
public void ToString_ReturnsDomainName(string domainName)
{
// Arrange
var domain = new Domain(1, domainName, "", "");
// Act
var result = domain.ToString();
// Assert
result.Should().Be(domainName);
}
}

View File

@@ -0,0 +1,104 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class NameserverTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Google DNS";
var nameServers = "8.8.8.8;8.8.4.4";
var description = "Google Public DNS";
var permissions = "{\"3\":\"2\"}";
var editDate = new DateTime(2026, 1, 10);
// Act
var nameserver = new Nameserver(id, name, nameServers, description, permissions, editDate);
// Assert
nameserver.Id.Should().Be(id);
nameserver.Name.Should().Be(name);
nameserver.Description.Should().Be(description);
nameserver.Permissions.Should().Be(permissions);
nameserver.EditDate.Should().Be(editDate);
}
[Fact]
public void Constructor_ParsesNameservers_BySemicolon()
{
// Arrange
var nameServersString = "8.8.8.8;8.8.4.4;1.1.1.1";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(3);
nameserver.NameServers.Should().ContainInOrder("8.8.8.8", "8.8.4.4", "1.1.1.1");
}
[Fact]
public void Constructor_WithSingleNameserver_ReturnsArrayWithOneElement()
{
// Arrange
var nameServersString = "8.8.8.8";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(1);
nameserver.NameServers[0].Should().Be("8.8.8.8");
}
[Fact]
public void Constructor_WithEmptyNameservers_ReturnsEmptyArray()
{
// Arrange
var nameServersString = "";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert - Empty entries are excluded
nameserver.NameServers.Should().BeEmpty();
}
[Fact]
public void Constructor_WithNullEditDate_SetsNull()
{
// Act
var nameserver = new Nameserver(1, "Test", "8.8.8.8", "", "", null);
// Assert
nameserver.EditDate.Should().BeNull();
}
[Fact]
public void Constructor_WithSemicolonOnlyString_ReturnsEmptyArray()
{
// Arrange - edge case with just semicolons
var nameServersString = ";;;";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().BeEmpty();
}
[Fact]
public void ToString_ReturnsName()
{
// Arrange
var nameserver = new Nameserver(1, "Google DNS", "8.8.8.8", "", "", null);
// Act & Assert
nameserver.ToString().Should().Be("Google DNS");
}
}

View File

@@ -0,0 +1,77 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SectionTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Production";
var description = "Production networks section";
var masterSectionId = 0;
var permissions = "{\"3\":\"2\"}";
var strictMode = true;
var subnetOrdering = "subnet,asc";
var order = 1;
var editDate = new DateTime(2026, 1, 5);
var showSubnet = true;
var showVlan = true;
var showVRF = false;
var showSupernetOnly = false;
var dnsId = 1;
// Act
var section = new Section(
id, name, description, masterSectionId, permissions, strictMode,
subnetOrdering, order, editDate, showSubnet, showVlan, showVRF,
showSupernetOnly, dnsId
);
// Assert
section.Id.Should().Be(id);
section.Name.Should().Be(name);
section.Description.Should().Be(description);
section.MasterSectionId.Should().Be(masterSectionId);
section.Permissions.Should().Be(permissions);
section.StrictMode.Should().Be(strictMode);
section.SubnetOrdering.Should().Be(subnetOrdering);
section.Order.Should().Be(order);
section.EditDate.Should().Be(editDate);
section.ShowSubnet.Should().Be(showSubnet);
section.ShowVlan.Should().Be(showVlan);
section.ShowVRF.Should().Be(showVRF);
section.ShowSupernetOnly.Should().Be(showSupernetOnly);
section.DNSId.Should().Be(dnsId);
}
[Theory]
[InlineData("Production")]
[InlineData("Development")]
[InlineData("DMZ")]
public void ToString_ReturnsSectionName(string sectionName)
{
// Arrange
var section = new Section(1, sectionName, "", 0, "", false, "", 0, null, false, false, false, false, 0);
// Act
var result = section.ToString();
// Assert
result.Should().Be(sectionName);
}
[Fact]
public void Constructor_WithNullEditDate_SetsNull()
{
// Act
var section = new Section(1, "Test", "", 0, "", false, "", 0, null, false, false, false, false, 0);
// Assert
section.EditDate.Should().BeNull();
}
}

View File

@@ -0,0 +1,90 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SessionTests
{
[Fact]
public void Constructor_WithCredentialsAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.Credentials;
var token = "test-token-123";
var appId = "myApp";
var url = "https://ipam.example.com";
var expires = new DateTime(2026, 12, 31, 23, 59, 59);
var credentials = new object(); // Mock credentials
// Act
var session = new Session(authType, token, appId, url, expires, credentials);
// Assert
session.AuthType.Should().Be(AuthType.Credentials);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
session.Expires.Should().Be(expires);
session.Credentials.Should().BeSameAs(credentials);
}
[Fact]
public void Constructor_WithTokenAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.Token;
var token = "static-api-token";
var appId = "apiApp";
var url = "https://ipam.test.com";
// Act
var session = new Session(authType, token, appId, url, null, null);
// Assert
session.AuthType.Should().Be(AuthType.Token);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
}
[Fact]
public void Token_CanBeModified()
{
// Arrange
var session = new Session(AuthType.Credentials, "old-token", "app", "https://test.com", null, null);
// Act
session.Token = "new-token";
// Assert
session.Token.Should().Be("new-token");
}
[Fact]
public void Expires_CanBeModified()
{
// Arrange
var session = new Session(AuthType.Credentials, "token", "app", "https://test.com", null, null);
var newExpiry = new DateTime(2027, 1, 1);
// Act
session.Expires = newExpiry;
// Assert
session.Expires.Should().Be(newExpiry);
}
[Fact]
public void Record_Equality_WorksForSameValues()
{
// Arrange
var session1 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
var session2 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
// Assert - Records use value equality
session1.Should().Be(session2);
}
}

View File

@@ -0,0 +1,157 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SubnetworkTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var subnet = "192.168.1.0";
var mask = 24;
var sectionId = 5;
var description = "Production network";
var linkedSubnet = "linked-123";
var firewallObject = "FW_PROD";
var vrfId = 2;
var masterSubnetId = 0;
var allowRequests = true;
var vlanId = 100;
var showName = true;
var deviceId = 10;
var permissions = "rw";
var pingSubnet = true;
var discoverSubnet = false;
var resolveDNS = true;
var dnsRecursive = false;
var dnsRecords = true;
var nameserverId = 3;
var scanAgent = false;
var isFolder = false;
var isFull = false;
var isPool = true;
var tagId = 1;
var threshold = 80;
var locationId = 4;
var editDate = new DateTime(2026, 1, 10);
var lastScan = new DateTime(2026, 1, 9);
var lastDiscovery = new DateTime(2026, 1, 8);
var calculation = new { maxhosts = 254 };
var customFields = new Dictionary<string, object> { { "custom_env", "prod" } };
// Act
var subnetwork = new Subnetwork(
id, subnet, mask, sectionId, description, linkedSubnet, firewallObject,
vrfId, masterSubnetId, allowRequests, vlanId, showName, deviceId,
permissions, pingSubnet, discoverSubnet, resolveDNS, dnsRecursive,
dnsRecords, nameserverId, scanAgent, isFolder, isFull, isPool,
tagId, threshold, locationId, editDate, lastScan, lastDiscovery,
calculation, customFields
);
// Assert
subnetwork.Id.Should().Be(id);
subnetwork.Subnet.Should().Be(subnet);
subnetwork.Mask.Should().Be(mask);
subnetwork.SectionId.Should().Be(sectionId);
subnetwork.Description.Should().Be(description);
subnetwork.LinkedSubnet.Should().Be(linkedSubnet);
subnetwork.FirewallAddressObject.Should().Be(firewallObject);
subnetwork.VrfId.Should().Be(vrfId);
subnetwork.MasterSubnetId.Should().Be(masterSubnetId);
subnetwork.AllowRequests.Should().Be(allowRequests);
subnetwork.VlanId.Should().Be(vlanId);
subnetwork.ShowName.Should().Be(showName);
subnetwork.DeviceId.Should().Be(deviceId);
subnetwork.Permissions.Should().Be(permissions);
subnetwork.PingSubnet.Should().Be(pingSubnet);
subnetwork.DiscoverSubnet.Should().Be(discoverSubnet);
subnetwork.ResolveDNS.Should().Be(resolveDNS);
subnetwork.DNSRecursive.Should().Be(dnsRecursive);
subnetwork.DNSRecords.Should().Be(dnsRecords);
subnetwork.NameserverId.Should().Be(nameserverId);
subnetwork.ScanAgent.Should().Be(scanAgent);
subnetwork.IsFolder.Should().Be(isFolder);
subnetwork.IsFull.Should().Be(isFull);
subnetwork.IsPool.Should().Be(isPool);
subnetwork.TagId.Should().Be(tagId);
subnetwork.Threshold.Should().Be(threshold);
subnetwork.LocationId.Should().Be(locationId);
subnetwork.EditDate.Should().Be(editDate);
subnetwork.LastScan.Should().Be(lastScan);
subnetwork.LastDiscovery.Should().Be(lastDiscovery);
subnetwork.Calculation.Should().BeEquivalentTo(calculation);
subnetwork.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Theory]
[InlineData("192.168.1.0", 24, "192.168.1.0/24")]
[InlineData("10.0.0.0", 8, "10.0.0.0/8")]
[InlineData("172.16.0.0", 16, "172.16.0.0/16")]
[InlineData("192.168.100.0", 30, "192.168.100.0/30")]
public void CIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr)
{
// Arrange
var subnetwork = CreateSubnetwork(subnet, mask);
// Act
var result = subnetwork.CIDR;
// Assert
result.Should().Be(expectedCidr);
}
[Fact]
public void ToString_ReturnsCidrNotation()
{
// Arrange
var subnetwork = CreateSubnetwork("10.10.0.0", 16);
// Act
var result = subnetwork.ToString();
// Assert
result.Should().Be("10.10.0.0/16");
}
[Fact]
public void ToString_EqualsCIDRProperty()
{
// Arrange
var subnetwork = CreateSubnetwork("172.20.0.0", 12);
// Act & Assert
subnetwork.ToString().Should().Be(subnetwork.CIDR);
}
[Fact]
public void Record_Equality_WorksCorrectly()
{
// Arrange - use same calculation object for equality comparison
var calculation = new { maxhosts = 254 };
var subnet1 = CreateSubnetwork("192.168.1.0", 24, calculation);
var subnet2 = CreateSubnetwork("192.168.1.0", 24, calculation);
// Assert - records use value equality (except for the Calculation object reference)
subnet1.Id.Should().Be(subnet2.Id);
subnet1.Subnet.Should().Be(subnet2.Subnet);
subnet1.Mask.Should().Be(subnet2.Mask);
subnet1.CIDR.Should().Be(subnet2.CIDR);
}
private static Subnetwork CreateSubnetwork(string subnet, int mask, object? calculation = null)
{
return new Subnetwork(
1, subnet, mask, 1, "", "", "",
0, 0, false, 0, false, 0,
"", false, false, false, false,
false, 0, false, false, false, false,
0, 0, 0, null, null, null,
calculation ?? new object(), null
);
}
}

View File

@@ -0,0 +1,63 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class TagTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var type = "Used";
var showTag = true;
var bgColor = "#5cb85c";
var fgColor = "#ffffff";
var compress = "Yes";
var locked = "No";
var updateTag = true;
// Act
var tag = new Tag(id, type, showTag, bgColor, fgColor, compress, locked, updateTag);
// Assert
tag.Id.Should().Be(id);
tag.Type.Should().Be(type);
tag.ShowTag.Should().Be(showTag);
tag.BackgroundColor.Should().Be(bgColor);
tag.ForegroundColor.Should().Be(fgColor);
tag.Compress.Should().Be(compress);
tag.Locked.Should().Be(locked);
tag.UpdateTag.Should().Be(updateTag);
}
[Theory]
[InlineData("Used")]
[InlineData("Available")]
[InlineData("Reserved")]
[InlineData("DHCP")]
public void ToString_ReturnsTagType(string tagType)
{
// Arrange
var tag = new Tag(1, tagType, false, "", "", "No", "No", false);
// Act
var result = tag.ToString();
// Assert
result.Should().Be(tagType);
}
[Fact]
public void Record_Equality_WorksCorrectly()
{
// Arrange
var tag1 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false);
var tag2 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false);
// Assert
tag1.Should().Be(tag2);
}
}

View File

@@ -0,0 +1,76 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class VlanTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var vlanId = 100;
var domainId = 1;
var name = "Production VLAN";
var number = 100;
var description = "Production network VLAN";
var editDate = new DateTime(2026, 1, 15);
var customerId = 50;
var customFields = new Dictionary<string, object> { { "custom_location", "DC1" } };
// Act
var vlan = new Vlan(vlanId, domainId, name, number, description, editDate, customerId, customFields);
// Assert
vlan.Id.Should().Be(vlanId);
vlan.VlanId.Should().Be(vlanId);
vlan.DomainId.Should().Be(domainId);
vlan.Name.Should().Be(name);
vlan.Number.Should().Be(number);
vlan.Description.Should().Be(description);
vlan.EditDate.Should().Be(editDate);
vlan.CustomerId.Should().Be(customerId);
vlan.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Fact]
public void Id_And_VlanId_AreSameValue()
{
// Arrange
var vlanId = 200;
// Act
var vlan = new Vlan(vlanId, 1, "Test", 200, "", null, 0, null);
// Assert
vlan.Id.Should().Be(vlan.VlanId);
}
[Theory]
[InlineData(1)]
[InlineData(100)]
[InlineData(4094)]
public void ToString_ReturnsVlanNumber(int vlanNumber)
{
// Arrange
var vlan = new Vlan(1, 1, "Test", vlanNumber, "", null, 0, null);
// Act
var result = vlan.ToString();
// Assert
result.Should().Be(vlanNumber.ToString());
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var vlan = new Vlan(1, 1, "Test", 10, "", null, 0, null);
// Assert
vlan.EditDate.Should().BeNull();
vlan.ExtendedData.Should().BeNull();
}
}

View File

@@ -0,0 +1,60 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class VrfTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "VRF_PROD";
var rd = "65000:100";
var description = "Production VRF";
var sections = "1;2;3";
var editDate = new DateTime(2026, 1, 10);
var customFields = new Dictionary<string, object> { { "custom_tenant", "CustomerA" } };
// Act
var vrf = new Vrf(id, name, rd, description, sections, editDate, customFields);
// Assert
vrf.Id.Should().Be(id);
vrf.Name.Should().Be(name);
vrf.RouteDistinguisher.Should().Be(rd);
vrf.Description.Should().Be(description);
vrf.Sections.Should().Be(sections);
vrf.EditDate.Should().Be(editDate);
vrf.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Theory]
[InlineData("VRF_DEFAULT")]
[InlineData("Production_VRF")]
[InlineData("CUSTOMER_A")]
public void ToString_ReturnsVrfName(string vrfName)
{
// Arrange
var vrf = new Vrf(1, vrfName, "65000:1", "", "", null, null);
// Act
var result = vrf.ToString();
// Assert
result.Should().Be(vrfName);
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var vrf = new Vrf(1, "Test", "65000:1", "", "", null, null);
// Assert
vrf.EditDate.Should().BeNull();
vrf.ExtendedData.Should().BeNull();
}
}

View File

@@ -0,0 +1,32 @@
namespace PS.IPAM.Tests;
using Xunit;
/// <summary>
/// Collection definition for tests that share static state (SessionManager, RequestHelper.TestHttpHandler).
/// Tests in this collection will run sequentially, not in parallel.
/// </summary>
[CollectionDefinition("Sequential")]
public class SequentialCollection : ICollectionFixture<SequentialTestFixture>
{
}
/// <summary>
/// Fixture for sequential test collection.
/// </summary>
public class SequentialTestFixture : IDisposable
{
public SequentialTestFixture()
{
// Clean up before tests
PS.IPAM.Helpers.SessionManager.CloseSession();
PS.IPAM.Helpers.RequestHelper.TestHttpHandler = null;
}
public void Dispose()
{
// Clean up after tests
PS.IPAM.Helpers.SessionManager.CloseSession();
PS.IPAM.Helpers.RequestHelper.TestHttpHandler = null;
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>PS.IPAM.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\classlib\classlib.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,60 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Assigns a tag to an address in phpIPAM.
/// </summary>
[Cmdlet("Assign", "Tag", SupportsShouldProcess = true)]
[OutputType(typeof(Address))]
public class AssignTagCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
HelpMessage = "The address to assign the tag to.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Mandatory = true,
Position = 1,
HelpMessage = "The tag to assign.")]
[ValidateNotNullOrEmpty]
public Tag? Tag { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new[] { AddressObject!.Id.ToString() };
var body = new Dictionary<string, object>
{
["tag"] = Tag!.Id
};
if (ShouldProcess($"Address {AddressObject.Ip}", $"Assign tag '{Tag.Type}'"))
{
RequestHelper.InvokeRequest(
"PATCH", ApiController.Addresses, null, null, body, identifiers
).GetAwaiter().GetResult();
// Return the updated address
var address = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Address, null, null, identifiers
).GetAwaiter().GetResult();
WriteResult(address);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "AssignTagError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,114 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Base class for all PS.IPAM cmdlets providing common functionality.
/// </summary>
public abstract class BaseCmdlet : PSCmdlet
{
/// <summary>
/// Writes the result to the pipeline, handling both single objects and collections.
/// </summary>
/// <param name="result">The result to write to the pipeline.</param>
protected void WriteResult(object? result)
{
if (result == null)
{
return;
}
if (result is IEnumerable enumerable && result is not string)
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
/// <summary>
/// Converts custom fields from various formats to a dictionary.
/// </summary>
/// <param name="customFields">The custom fields object to convert.</param>
/// <returns>A dictionary of custom field names and values.</returns>
protected static Dictionary<string, object> ConvertCustomFields(object? customFields)
{
if (customFields == null)
{
return new Dictionary<string, object>();
}
if (customFields is PSObject psobj)
{
var dict = new Dictionary<string, object>();
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? string.Empty;
}
return dict;
}
if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
if (customFields is IDictionary genericDict)
{
var result = new Dictionary<string, object>();
foreach (DictionaryEntry entry in genericDict)
{
result[entry.Key?.ToString() ?? string.Empty] = entry.Value ?? string.Empty;
}
return result;
}
return new Dictionary<string, object>();
}
/// <summary>
/// Executes an async operation synchronously and handles errors.
/// </summary>
/// <typeparam name="T">The return type of the operation.</typeparam>
/// <param name="operation">The async operation to execute.</param>
/// <param name="errorId">The error ID for error reporting.</param>
/// <returns>The result of the operation, or default if an error occurred.</returns>
protected T? ExecuteAsync<T>(Func<System.Threading.Tasks.Task<T>> operation, string errorId)
{
try
{
return operation().GetAwaiter().GetResult();
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, errorId, ErrorCategory.InvalidOperation, null));
return default;
}
}
/// <summary>
/// Executes an async operation synchronously and handles errors.
/// </summary>
/// <param name="operation">The async operation to execute.</param>
/// <param name="errorId">The error ID for error reporting.</param>
protected void ExecuteAsync(Func<System.Threading.Tasks.Task> operation, string errorId)
{
try
{
operation().GetAwaiter().GetResult();
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, errorId, ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,17 @@
namespace PS.IPAM.Cmdlets;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Closes the current phpIPAM API session.
/// </summary>
[Cmdlet(VerbsCommon.Close, "Session")]
public class CloseSessionCmdlet : BaseCmdlet
{
protected override void ProcessRecord()
{
SessionManager.CloseSession();
WriteVerbose("Session closed successfully.");
}
}

View File

@@ -0,0 +1,169 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves IP address entries from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Address", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Address))]
public class GetAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The address ID to retrieve.")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP",
HelpMessage = "The IP address to search for.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "BySubnetId")]
[ValidateNotNullOrEmpty]
public IPAddress? IP { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostName",
HelpMessage = "The hostname to search for.")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostBase",
HelpMessage = "The hostname base pattern to search for.")]
[ValidateNotNullOrEmpty]
public string? HostBase { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag",
HelpMessage = "The tag ID to filter addresses by.")]
[ValidateNotNullOrEmpty]
public int? TagId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId",
HelpMessage = "The subnet ID to get addresses from.")]
[ValidateNotNullOrEmpty]
public int? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR",
HelpMessage = "The subnet in CIDR notation to get addresses from.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = ApiController.Addresses;
var identifiers = new List<string>();
switch (ParameterSetName)
{
case "ByID":
identifiers.Add(Id.ToString());
break;
case "ByIP":
identifiers.Add("search");
identifiers.Add(IP!.ToString());
break;
case "ByHostName":
identifiers.Add("search_hostname");
identifiers.Add(HostName!);
break;
case "ByHostBase":
identifiers.Add("search_hostbase");
identifiers.Add(HostBase!);
break;
case "ByTag":
identifiers.Add("tags");
identifiers.Add(TagId!.Value.ToString());
identifiers.Add("addresses");
break;
case "BySubnetId":
if (IP != null)
{
identifiers.Add(IP.ToString());
identifiers.Add(SubnetId!.Value.ToString());
}
else
{
controller = ApiController.Subnets;
identifiers.Add(SubnetId!.Value.ToString());
identifiers.Add("addresses");
}
break;
case "BySubnetCIDR":
controller = ApiController.Subnets;
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", SubnetCIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found.");
}
identifiers.Add(subnet.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest(
"GET", controller, ModelType.Address, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetAddressError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,90 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Gets the first available IP address in a subnet.
/// </summary>
[Cmdlet(VerbsCommon.Get, "FirstFreeIP", DefaultParameterSetName = "ByID")]
public class GetFirstFreeIPCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR",
HelpMessage = "The subnet in CIDR notation.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? CIDR { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject",
HelpMessage = "The subnet object.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
protected override void ProcessRecord()
{
try
{
var subnetId = GetSubnetId();
var identifiers = new List<string> { subnetId.ToString(), "first_free" };
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, null, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
if (result != null)
{
WriteObject(new { Ip = result.ToString() });
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetFirstFreeIPError", ErrorCategory.InvalidOperation, null));
}
}
private int GetSubnetId()
{
switch (ParameterSetName)
{
case "ByCIDR":
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", CIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new ItemNotFoundException($"Subnet '{CIDR}' not found.");
}
return subnet.Id;
case "BySubnetObject":
return SubnetObject!.Id;
default:
return Id!.Value;
}
}
}

View File

@@ -0,0 +1,48 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves L2 domain information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "L2Domain", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Domain))]
public class GetL2DomainCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The L2 domain ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string>();
if (Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.L2Domains, ModelType.Domain, null, null,
identifiers.Count > 0 ? identifiers.ToArray() : null
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetL2DomainError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,48 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves nameserver information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Nameserver", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Nameserver))]
public class GetNameserverCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The nameserver ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string>();
if (Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Tools, ModelType.Nameserver, ApiSubController.Nameservers,
null, identifiers.Count > 0 ? identifiers.ToArray() : null
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetNameserverError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,153 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves permission information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Permissions")]
public class GetPermissionsCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The address ID.")]
[ValidateNotNullOrEmpty]
public string? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP",
HelpMessage = "The IP address to search for.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "BySubnetId")]
[ValidateNotNullOrEmpty]
public IPAddress? IP { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostName",
HelpMessage = "The hostname to search for.")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag",
HelpMessage = "The tag ID.")]
[ValidateNotNullOrEmpty]
public string? TagId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public string? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR",
HelpMessage = "The subnet in CIDR notation.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = ApiController.Addresses;
var identifiers = new List<string>();
switch (ParameterSetName)
{
case "ByID":
identifiers.Add(Id!);
break;
case "ByIP":
identifiers.Add("search");
identifiers.Add(IP!.ToString());
break;
case "ByHostName":
identifiers.Add("search_hostname");
identifiers.Add(HostName!);
break;
case "ByTag":
identifiers.Add("tags");
identifiers.Add(TagId!);
identifiers.Add("addresses");
break;
case "BySubnetId":
if (IP != null)
{
identifiers.Add(IP.ToString());
identifiers.Add(SubnetId!);
}
else
{
controller = ApiController.Subnets;
identifiers.Add(SubnetId!);
identifiers.Add("addresses");
}
break;
case "BySubnetCIDR":
controller = ApiController.Subnets;
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", SubnetCIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found.");
}
identifiers.Add(subnet.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest(
"GET", controller, null, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetPermissionsError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,60 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves section information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Section", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Section))]
public class GetSectionCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The section ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByName",
HelpMessage = "The section name.")]
[ValidateNotNullOrEmpty]
public string? Name { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string>();
if (ParameterSetName == "ByID" && Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
}
else if (ParameterSetName == "ByName")
{
identifiers.Add(Name!);
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Sections, ModelType.Section, null, null,
identifiers.Count > 0 ? identifiers.ToArray() : null
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSectionError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,277 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves subnet information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Subnet", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Subnetwork))]
public class GetSubnetCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR",
HelpMessage = "The subnet in CIDR notation (e.g., 192.168.1.0/24).")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? CIDR { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySectionId",
HelpMessage = "The section ID to get subnets from.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "ByVlanNumber")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByVlanId")]
[ValidateNotNullOrEmpty]
public int? SectionId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySectionName",
HelpMessage = "The section name to get subnets from.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3,
ParameterSetName = "ByVlanNumber")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "ByVlanId")]
[ValidateNotNullOrEmpty]
public string? SectionName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVrfId",
HelpMessage = "The VRF ID to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VrfId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanId",
HelpMessage = "The VLAN ID to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VlanId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanNumber",
HelpMessage = "The VLAN number to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VlanNumber { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByID",
HelpMessage = "Get child subnets (slaves).")]
public SwitchParameter Slaves { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "ByID",
HelpMessage = "Get child subnets recursively.")]
public SwitchParameter Recurse { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByVlanNumber",
HelpMessage = "The L2 domain ID to narrow VLAN search.")]
[ValidateNotNullOrEmpty]
public int? VlanDomainId { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = ApiController.Subnets;
var identifiers = new List<string>();
switch (ParameterSetName)
{
case "ByCIDR":
identifiers.Add("cidr");
identifiers.Add(CIDR!);
break;
case "ByID":
identifiers.Add(Id!.Value.ToString());
if (Slaves.IsPresent)
{
identifiers.Add(Recurse.IsPresent ? "slaves_recursive" : "slaves");
}
break;
case "BySectionId":
controller = ApiController.Sections;
identifiers.Add(SectionId!.Value.ToString());
identifiers.Add("subnets");
break;
case "BySectionName":
controller = ApiController.Sections;
var section = GetSectionByName(SectionName!);
identifiers.Add(section.Id.ToString());
identifiers.Add("subnets");
break;
case "ByVrfId":
controller = ApiController.Vrf;
identifiers.Add(VrfId!.Value.ToString());
identifiers.Add("subnets");
break;
case "ByVlanId":
controller = ApiController.Vlan;
identifiers.Add(VlanId!.Value.ToString());
identifiers.Add("subnets");
AddSectionIdentifier(identifiers);
break;
case "ByVlanNumber":
controller = ApiController.Vlan;
var vlan = FindVlanByNumber(VlanNumber!.Value, VlanDomainId);
identifiers.Add(vlan.Id.ToString());
identifiers.Add("subnets");
AddSectionIdentifier(identifiers);
break;
}
var result = RequestHelper.InvokeRequest(
"GET", controller, ModelType.Subnetwork, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Section GetSectionByName(string name)
{
var section = RequestHelper.InvokeRequest(
"GET", ApiController.Sections, ModelType.Section, null, null, new[] { name }
).GetAwaiter().GetResult() as Section;
if (section == null)
{
throw new ItemNotFoundException($"Section '{name}' not found.");
}
return section;
}
private Vlan FindVlanByNumber(int number, int? domainId)
{
var vlans = RequestHelper.InvokeRequest(
"GET", ApiController.Vlan, ModelType.Vlan, null, null,
new[] { "search", number.ToString() }
).GetAwaiter().GetResult();
if (vlans == null)
{
throw new ItemNotFoundException($"VLAN {number} not found.");
}
Vlan? foundVlan = null;
if (vlans is System.Collections.IEnumerable enumerable)
{
foreach (var v in enumerable)
{
if (v is Vlan vlan)
{
if (domainId.HasValue && vlan.DomainId != domainId.Value)
{
continue;
}
if (foundVlan != null)
{
throw new InvalidOperationException(
$"Multiple VLANs with number {number} exist. Specify VlanDomainId to narrow the search.");
}
foundVlan = vlan;
}
}
}
if (foundVlan == null)
{
throw new ItemNotFoundException($"VLAN {number} not found.");
}
return foundVlan;
}
private void AddSectionIdentifier(List<string> identifiers)
{
if (SectionId.HasValue)
{
identifiers.Add(SectionId.Value.ToString());
}
else if (!string.IsNullOrEmpty(SectionName))
{
var section = GetSectionByName(SectionName);
identifiers.Add(section.Id.ToString());
}
}
}

View File

@@ -0,0 +1,86 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves subnet usage statistics from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "SubnetUsage", DefaultParameterSetName = "ByID")]
public class GetSubnetUsageCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR",
HelpMessage = "The subnet in CIDR notation.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? CIDR { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "BySubnetObject",
HelpMessage = "The subnet object.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
protected override void ProcessRecord()
{
try
{
var subnetId = GetSubnetId();
var identifiers = new List<string> { subnetId.ToString(), "usage" };
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, null, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSubnetUsageError", ErrorCategory.InvalidOperation, null));
}
}
private int GetSubnetId()
{
switch (ParameterSetName)
{
case "ByCIDR":
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", CIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new ItemNotFoundException($"Subnet '{CIDR}' not found.");
}
return subnet.Id;
case "BySubnetObject":
return SubnetObject!.Id;
default:
return Id!.Value;
}
}
}

View File

@@ -0,0 +1,94 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves tag information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Tag", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Tag))]
public class GetTagCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The tag ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject",
HelpMessage = "Get the tag associated with this address.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject",
HelpMessage = "Get the tag associated with this subnet.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string> { "tags" };
switch (ParameterSetName)
{
case "ByID":
if (Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
}
break;
case "ByAddressObject":
if (AddressObject?.TagId > 0)
{
identifiers.Add(AddressObject.TagId.ToString());
}
else
{
return;
}
break;
case "BySubnetObject":
if (SubnetObject?.TagId > 0)
{
identifiers.Add(SubnetObject.TagId.ToString());
}
else
{
return;
}
break;
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Tag, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetTagError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,121 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves VLAN information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Vlan", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vlan))]
public class GetVlanCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The VLAN ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByNumber",
HelpMessage = "The VLAN number.")]
[ValidateNotNullOrEmpty]
public int? Number { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByL2Domain",
HelpMessage = "The L2 domain ID to get VLANs from.")]
[ValidateNotNullOrEmpty]
public int? L2DomainId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject",
HelpMessage = "Get the VLAN associated with this subnet.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByDomainObject",
HelpMessage = "Get VLANs in this L2 domain.")]
[ValidateNotNullOrEmpty]
public Domain? DomainObject { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = ApiController.Vlan;
var identifiers = new List<string>();
switch (ParameterSetName)
{
case "ByID":
identifiers.Add(Id!.Value.ToString());
break;
case "ByNumber":
identifiers.Add("search");
identifiers.Add(Number!.Value.ToString());
break;
case "ByL2Domain":
controller = ApiController.L2Domains;
identifiers.Add(L2DomainId!.Value.ToString());
identifiers.Add(ApiSubController.Vlans.ToString().ToLowerInvariant());
break;
case "BySubnetObject":
if (SubnetObject == null || SubnetObject.VlanId <= 0)
{
return;
}
identifiers.Add(SubnetObject.VlanId.ToString());
break;
case "ByDomainObject":
if (DomainObject != null)
{
var result = RequestHelper.InvokeRequest(
"GET", ApiController.L2Domains, ModelType.Vlan, ApiSubController.Vlans,
null, new[] { DomainObject.Id.ToString() }
).GetAwaiter().GetResult();
WriteResult(result);
}
return;
}
var vlanResult = RequestHelper.InvokeRequest(
"GET", controller, ModelType.Vlan, null, null,
identifiers.Count > 0 ? identifiers.ToArray() : null
).GetAwaiter().GetResult();
WriteResult(vlanResult);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetVlanError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,48 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves VRF information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Vrf", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vrf))]
public class GetVrfCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID",
HelpMessage = "The VRF ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string>();
if (Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Vrf, ModelType.Vrf, null, null,
identifiers.Count > 0 ? identifiers.ToArray() : null
).GetAwaiter().GetResult();
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetVrfError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,142 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Creates a new IP address entry in phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.New, "Address", DefaultParameterSetName = "BySubnetId")]
[OutputType(typeof(Address))]
public class NewAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId",
HelpMessage = "The subnet ID to create the address in.")]
[ValidateNotNullOrEmpty]
public int? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject",
HelpMessage = "The subnet object to create the address in.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
HelpMessage = "The IP address to create.")]
[ValidateNotNullOrEmpty]
public string IP { get; set; } = string.Empty;
[Parameter(Mandatory = false, Position = 2, HelpMessage = "Mark this address as a gateway.")]
public SwitchParameter Gateway { get; set; }
[Parameter(Mandatory = false, Position = 3, HelpMessage = "Description for the address.")]
public string? Description { get; set; }
[Parameter(Mandatory = false, Position = 4, HelpMessage = "Hostname for the address.")]
public string? Hostname { get; set; }
[Parameter(Mandatory = false, Position = 5, HelpMessage = "MAC address.")]
public string? MAC { get; set; }
[Parameter(Mandatory = false, Position = 6, HelpMessage = "Owner of the address.")]
public string? Owner { get; set; }
[Parameter(Mandatory = false, Position = 7, HelpMessage = "Tag ID for the address.")]
public int? TagId { get; set; }
[Parameter(Mandatory = false, Position = 8, HelpMessage = "Ignore PTR record generation.")]
public SwitchParameter PTRIgnore { get; set; }
[Parameter(Mandatory = false, Position = 9, HelpMessage = "PTR record ID.")]
public int? PTRId { get; set; }
[Parameter(Mandatory = false, Position = 10, HelpMessage = "Note for the address.")]
public string? Note { get; set; }
[Parameter(Mandatory = false, Position = 11, HelpMessage = "Exclude from ping scanning.")]
public SwitchParameter ExcludePing { get; set; }
[Parameter(Mandatory = false, Position = 12, HelpMessage = "Associated device ID.")]
public int? DeviceId { get; set; }
[Parameter(Mandatory = false, Position = 13, HelpMessage = "Port information.")]
public string? Port { get; set; }
[Parameter(Mandatory = false, Position = 14, HelpMessage = "Custom fields as a hashtable or PSObject.")]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var actualSubnetId = ParameterSetName == "BySubnetObject"
? SubnetObject!.Id
: SubnetId!.Value;
var body = BuildRequestBody(actualSubnetId);
var result = RequestHelper.InvokeRequest(
"POST", ApiController.Addresses, null, null, body
).GetAwaiter().GetResult();
if (result != null)
{
// Fetch the created address to return it
var address = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Address, null, null,
new[] { "search", IP }
).GetAwaiter().GetResult();
WriteResult(address);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewAddressError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> BuildRequestBody(int subnetId)
{
var body = new Dictionary<string, object>
{
["subnetId"] = subnetId,
["ip"] = IP
};
if (Gateway.IsPresent) body["is_gateway"] = "1";
if (!string.IsNullOrEmpty(Description)) body["description"] = Description;
if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname;
if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC;
if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner;
if (TagId.HasValue) body["tag"] = TagId.Value;
if (PTRIgnore.IsPresent) body["PTRignore"] = "1";
if (PTRId.HasValue) body["PTR"] = PTRId.Value;
if (!string.IsNullOrEmpty(Note)) body["note"] = Note;
if (ExcludePing.IsPresent) body["excludePing"] = "1";
if (DeviceId.HasValue) body["deviceId"] = DeviceId.Value;
if (!string.IsNullOrEmpty(Port)) body["port"] = Port;
foreach (var kvp in ConvertCustomFields(CustomFields))
{
body[kvp.Key] = kvp.Value;
}
return body;
}
}

View File

@@ -0,0 +1,118 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Creates a new address using the first available IP in a subnet.
/// </summary>
[Cmdlet(VerbsCommon.New, "FirstFreeIP")]
[OutputType(typeof(Address))]
public class NewFirstFreeIPCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
HelpMessage = "The subnet ID to create the address in.")]
[ValidateNotNullOrEmpty]
public string SubnetId { get; set; } = string.Empty;
[Parameter(Mandatory = false, Position = 1, HelpMessage = "Mark this address as a gateway.")]
public SwitchParameter Gateway { get; set; }
[Parameter(Mandatory = false, Position = 2, HelpMessage = "Description for the address.")]
public string? Description { get; set; }
[Parameter(Mandatory = false, Position = 3, HelpMessage = "Hostname for the address.")]
public string? Hostname { get; set; }
[Parameter(Mandatory = false, Position = 4, HelpMessage = "MAC address.")]
public string? MAC { get; set; }
[Parameter(Mandatory = false, Position = 5, HelpMessage = "Owner of the address.")]
public string? Owner { get; set; }
[Parameter(Mandatory = false, Position = 6, HelpMessage = "Tag ID for the address.")]
public string? TagId { get; set; }
[Parameter(Mandatory = false, Position = 7, HelpMessage = "Ignore PTR record generation.")]
public SwitchParameter PTRIgnore { get; set; }
[Parameter(Mandatory = false, Position = 8, HelpMessage = "PTR record ID.")]
public string? PTRId { get; set; }
[Parameter(Mandatory = false, Position = 9, HelpMessage = "Note for the address.")]
public string? Note { get; set; }
[Parameter(Mandatory = false, Position = 10, HelpMessage = "Exclude from ping scanning.")]
public SwitchParameter ExcludePing { get; set; }
[Parameter(Mandatory = false, Position = 11, HelpMessage = "Associated device ID.")]
public string? DeviceId { get; set; }
[Parameter(Mandatory = false, Position = 12, HelpMessage = "Port information.")]
public string? Port { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new[] { "first_free" };
var body = BuildRequestBody();
var result = RequestHelper.InvokeRequest(
"POST", ApiController.Addresses, null, null, body, identifiers
).GetAwaiter().GetResult();
if (result != null)
{
var ip = result.ToString();
var address = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Address, null, null,
new[] { "search", ip! }
).GetAwaiter().GetResult();
WriteResult(address);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewFirstFreeIPError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> BuildRequestBody()
{
var body = new Dictionary<string, object>
{
["subnetId"] = SubnetId
};
if (Gateway.IsPresent) body["is_gateway"] = "1";
if (!string.IsNullOrEmpty(Description)) body["description"] = Description;
if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname;
if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC;
if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner;
if (!string.IsNullOrEmpty(TagId)) body["tag"] = TagId;
if (PTRIgnore.IsPresent) body["PTRignore"] = "1";
if (!string.IsNullOrEmpty(PTRId)) body["PTR"] = PTRId;
if (!string.IsNullOrEmpty(Note)) body["note"] = Note;
if (ExcludePing.IsPresent) body["excludePing"] = "1";
if (!string.IsNullOrEmpty(DeviceId)) body["deviceId"] = DeviceId;
if (!string.IsNullOrEmpty(Port)) body["port"] = Port;
foreach (var kvp in ConvertCustomFields(CustomFields))
{
body[kvp.Key] = kvp.Value;
}
return body;
}
}

View File

@@ -0,0 +1,92 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Creates a new phpIPAM API session.
/// </summary>
[Cmdlet(VerbsCommon.New, "Session", DefaultParameterSetName = "Credentials")]
[OutputType(typeof(Session))]
public class NewSessionCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
HelpMessage = "The phpIPAM server URL (must start with http:// or https://).")]
[ValidateNotNullOrEmpty]
[ValidatePattern("^https?://")]
public string URL { get; set; } = string.Empty;
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
HelpMessage = "The API application ID configured in phpIPAM.")]
[ValidateNotNullOrEmpty]
public string AppID { get; set; } = string.Empty;
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "Credentials",
HelpMessage = "The credentials (username and password) for authentication.")]
[ValidateNotNullOrEmpty]
public PSCredential? Credentials { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "Token",
HelpMessage = "The static API token for authentication.")]
[ValidateNotNullOrEmpty]
public string? Token { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3,
HelpMessage = "If specified, SSL certificate errors will be ignored.")]
public SwitchParameter IgnoreSSL { get; set; }
protected override void ProcessRecord()
{
try
{
Session session;
if (ParameterSetName == "Credentials" && Credentials != null)
{
session = SessionManager.CreateSessionWithCredentialsAsync(
URL,
AppID,
Credentials,
IgnoreSSL.IsPresent
).GetAwaiter().GetResult();
}
else if (ParameterSetName == "Token" && Token != null)
{
session = SessionManager.CreateSessionWithToken(URL, AppID, Token);
}
else
{
throw new ArgumentException("Invalid parameter set. Provide either Credentials or Token.");
}
WriteObject(session);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewSessionError", ErrorCategory.AuthenticationError, null));
}
}
}

View File

@@ -0,0 +1,143 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Creates a new subnet in phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.New, "Subnet")]
[OutputType(typeof(Subnetwork))]
public class NewSubnetCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
HelpMessage = "The subnet in CIDR notation (e.g., 192.168.1.0/24).")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string CIDR { get; set; } = string.Empty;
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
HelpMessage = "The section ID to create the subnet in.")]
[ValidateNotNullOrEmpty]
public int SectionId { get; set; }
[Parameter(Mandatory = false, Position = 2, HelpMessage = "Description for the subnet.")]
public string? Description { get; set; }
[Parameter(Mandatory = false, Position = 3, HelpMessage = "VLAN ID to associate.")]
public int? VlanId { get; set; }
[Parameter(Mandatory = false, Position = 4, HelpMessage = "VRF ID to associate.")]
public int? VrfId { get; set; }
[Parameter(Mandatory = false, Position = 5, HelpMessage = "Master subnet ID for hierarchy.")]
public int? MasterSubnetId { get; set; }
[Parameter(Mandatory = false, Position = 6, HelpMessage = "Nameserver ID.")]
public int? NameserverId { get; set; }
[Parameter(Mandatory = false, Position = 7, HelpMessage = "Show subnet name.")]
public SwitchParameter ShowName { get; set; }
[Parameter(Mandatory = false, Position = 8, HelpMessage = "Enable recursive DNS.")]
public SwitchParameter DNSRecursive { get; set; }
[Parameter(Mandatory = false, Position = 9, HelpMessage = "Enable DNS records.")]
public SwitchParameter DNSRecords { get; set; }
[Parameter(Mandatory = false, Position = 10, HelpMessage = "Allow IP requests.")]
public SwitchParameter AllowRequests { get; set; }
[Parameter(Mandatory = false, Position = 11, HelpMessage = "Scan agent ID.")]
public int? ScanAgentId { get; set; }
[Parameter(Mandatory = false, Position = 12, HelpMessage = "Enable subnet discovery.")]
public SwitchParameter DiscoverSubnet { get; set; }
[Parameter(Mandatory = false, Position = 13, HelpMessage = "Mark subnet as full.")]
public SwitchParameter IsFull { get; set; }
[Parameter(Mandatory = false, Position = 14, HelpMessage = "Tag ID for the subnet.")]
public int? TagId { get; set; }
[Parameter(Mandatory = false, Position = 15, HelpMessage = "Usage threshold percentage (1-100).")]
[ValidateRange(1, 100)]
public int? Threshold { get; set; }
[Parameter(Mandatory = false, Position = 16, HelpMessage = "Location ID.")]
public int? LocationId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var body = BuildRequestBody();
var result = RequestHelper.InvokeRequest(
"POST", ApiController.Subnets, null, null, body
).GetAwaiter().GetResult();
if (result != null)
{
// Get the created subnet
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", CIDR }
).GetAwaiter().GetResult();
WriteResult(subnet);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> BuildRequestBody()
{
var parts = CIDR.Split('/');
var body = new Dictionary<string, object>
{
["subnet"] = parts[0],
["mask"] = parts[1],
["sectionId"] = SectionId
};
if (!string.IsNullOrEmpty(Description)) body["description"] = Description;
if (VlanId.HasValue) body["vlanId"] = VlanId.Value;
if (VrfId.HasValue) body["vrfId"] = VrfId.Value;
if (MasterSubnetId.HasValue) body["masterSubnetId"] = MasterSubnetId.Value;
if (NameserverId.HasValue) body["nameserverId"] = NameserverId.Value;
if (ShowName.IsPresent) body["showName"] = "1";
if (DNSRecursive.IsPresent) body["DNSrecursive"] = "1";
if (DNSRecords.IsPresent) body["DNSrecords"] = "1";
if (AllowRequests.IsPresent) body["allowRequests"] = "1";
if (ScanAgentId.HasValue) body["scanAgent"] = ScanAgentId.Value;
if (DiscoverSubnet.IsPresent) body["discoverSubnet"] = "1";
if (IsFull.IsPresent) body["isFull"] = "1";
if (TagId.HasValue) body["state"] = TagId.Value;
if (Threshold.HasValue) body["threshold"] = Threshold.Value;
if (LocationId.HasValue) body["location"] = LocationId.Value;
foreach (var kvp in ConvertCustomFields(CustomFields))
{
body[kvp.Key] = kvp.Value;
}
return body;
}
}

View File

@@ -0,0 +1,53 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Removes an IP address entry from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Remove, "Address", DefaultParameterSetName = "ById", SupportsShouldProcess = true)]
public class RemoveAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ById",
HelpMessage = "The address ID to remove.")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByAddressObject",
HelpMessage = "The address object to remove.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
protected override void ProcessRecord()
{
try
{
var addressId = ParameterSetName == "ById" ? Id : AddressObject!.Id;
var identifiers = new[] { addressId.ToString() };
if (ShouldProcess($"Address ID: {addressId}", "Remove"))
{
RequestHelper.InvokeRequest(
"DELETE", ApiController.Addresses, null, null, null, identifiers
).GetAwaiter().GetResult();
WriteVerbose($"Address {addressId} removed successfully.");
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "RemoveAddressError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,128 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Updates an existing IP address entry in phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Set, "Address", DefaultParameterSetName = "ById")]
[OutputType(typeof(Address))]
public class SetAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ById",
HelpMessage = "The address ID to update.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject",
HelpMessage = "The address object to update.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(Position = 1, HelpMessage = "Set as gateway address.")]
public bool? Gateway { get; set; }
[Parameter(Position = 2, HelpMessage = "Description for the address.")]
public string? Description { get; set; }
[Parameter(Position = 3, HelpMessage = "Hostname for the address.")]
public string? Hostname { get; set; }
[Parameter(Position = 4, HelpMessage = "MAC address.")]
public string? MAC { get; set; }
[Parameter(Position = 5, HelpMessage = "Owner of the address.")]
public string? Owner { get; set; }
[Parameter(Position = 6, HelpMessage = "Tag ID for the address.")]
public int? TagId { get; set; }
[Parameter(Position = 7, HelpMessage = "Ignore PTR record generation.")]
public bool? PTRIgnore { get; set; }
[Parameter(Position = 8, HelpMessage = "PTR record ID.")]
public int? PTRId { get; set; }
[Parameter(Position = 9, HelpMessage = "Note for the address.")]
public string? Note { get; set; }
[Parameter(Position = 10, HelpMessage = "Exclude from ping scanning.")]
public bool? ExcludePing { get; set; }
[Parameter(Position = 11, HelpMessage = "Associated device ID.")]
public int? DeviceId { get; set; }
[Parameter(Position = 12, HelpMessage = "Port information.")]
public string? Port { get; set; }
[Parameter(HelpMessage = "Custom fields as a hashtable or PSObject.")]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var addressId = ParameterSetName == "ById" ? Id!.Value : AddressObject!.Id;
var identifiers = new[] { addressId.ToString() };
var body = BuildRequestBody();
try
{
RequestHelper.InvokeRequest(
"PATCH", ApiController.Addresses, null, null, body, identifiers
).GetAwaiter().GetResult();
}
finally
{
// Always return the updated address
var address = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Address, null, null, identifiers
).GetAwaiter().GetResult();
WriteResult(address);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "SetAddressError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> BuildRequestBody()
{
var body = new Dictionary<string, object>();
if (Gateway.HasValue) body["is_gateway"] = Gateway.Value;
if (!string.IsNullOrEmpty(Description)) body["description"] = Description;
if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname;
if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC;
if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner;
if (TagId.HasValue) body["tag"] = TagId.Value;
if (PTRIgnore.HasValue) body["PTRignore"] = PTRIgnore.Value;
if (PTRId.HasValue) body["PTR"] = PTRId.Value;
if (!string.IsNullOrEmpty(Note)) body["note"] = Note;
if (ExcludePing.HasValue) body["excludePing"] = ExcludePing.Value;
if (DeviceId.HasValue) body["deviceId"] = DeviceId.Value;
if (!string.IsNullOrEmpty(Port)) body["port"] = Port;
foreach (var kvp in ConvertCustomFields(CustomFields))
{
body[kvp.Key] = kvp.Value;
}
return body;
}
}

View File

@@ -0,0 +1,123 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM.Helpers;
/// <summary>
/// Updates an existing subnet in phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Set, "Subnet")]
[OutputType(typeof(Subnetwork))]
public class SetSubnetCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
HelpMessage = "The subnet ID to update.")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Description for the subnet.")]
public string? Description { get; set; }
[Parameter(Mandatory = false, HelpMessage = "VLAN ID to associate.")]
public int? VlanId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "VRF ID to associate.")]
public int? VrfId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Master subnet ID for hierarchy.")]
public int? MasterSubnetId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Nameserver ID.")]
public int? NameserverId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Show subnet name.")]
public SwitchParameter ShowName { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable recursive DNS.")]
public SwitchParameter DNSRecursive { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable DNS records.")]
public SwitchParameter DNSRecords { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Allow IP requests.")]
public SwitchParameter AllowRequests { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Scan agent ID.")]
public int? ScanAgentId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable subnet discovery.")]
public SwitchParameter DiscoverSubnet { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Mark subnet as full.")]
public SwitchParameter IsFull { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Tag ID for the subnet.")]
public int? TagId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Usage threshold percentage (1-100).")]
[ValidateRange(1, 100)]
public int? Threshold { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Location ID.")]
public int? LocationId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new[] { Id.ToString() };
var body = BuildRequestBody();
RequestHelper.InvokeRequest(
"PATCH", ApiController.Subnets, null, null, body, identifiers
).GetAwaiter().GetResult();
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null, identifiers
).GetAwaiter().GetResult();
WriteResult(subnet);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "SetSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> BuildRequestBody()
{
var body = new Dictionary<string, object>();
if (!string.IsNullOrEmpty(Description)) body["description"] = Description;
if (VlanId.HasValue) body["vlanId"] = VlanId.Value;
if (VrfId.HasValue) body["vrfId"] = VrfId.Value;
if (MasterSubnetId.HasValue) body["masterSubnetId"] = MasterSubnetId.Value;
if (NameserverId.HasValue) body["nameserverId"] = NameserverId.Value;
if (ShowName.IsPresent) body["showName"] = "1";
if (DNSRecursive.IsPresent) body["DNSrecursive"] = "1";
if (DNSRecords.IsPresent) body["DNSrecords"] = "1";
if (AllowRequests.IsPresent) body["allowRequests"] = "1";
if (ScanAgentId.HasValue) body["scanAgent"] = ScanAgentId.Value;
if (DiscoverSubnet.IsPresent) body["discoverSubnet"] = "1";
if (IsFull.IsPresent) body["isFull"] = "1";
if (TagId.HasValue) body["state"] = TagId.Value;
if (Threshold.HasValue) body["threshold"] = Threshold.Value;
if (LocationId.HasValue) body["location"] = LocationId.Value;
foreach (var kvp in ConvertCustomFields(CustomFields))
{
body[kvp.Key] = kvp.Value;
}
return body;
}
}

View File

@@ -0,0 +1,441 @@
namespace PS.IPAM.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
/// <summary>
/// Helper class for making HTTP requests to the phpIPAM API.
/// </summary>
public static class RequestHelper
{
/// <summary>
/// Handler for testing - allows injecting a mock HTTP handler.
/// </summary>
public static HttpMessageHandler? TestHttpHandler { get; set; }
/// <summary>
/// Invokes an HTTP request to the phpIPAM API.
/// </summary>
/// <param name="method">The HTTP method (GET, POST, PATCH, DELETE).</param>
/// <param name="controller">The API controller to call.</param>
/// <param name="modelType">The expected model type for response conversion.</param>
/// <param name="subController">Optional sub-controller for nested endpoints.</param>
/// <param name="parameters">Optional request body parameters.</param>
/// <param name="identifiers">Optional path identifiers.</param>
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
/// <returns>The deserialized response data, or null if not found.</returns>
public static async Task<object?> InvokeRequestAsync(
HttpMethod method,
ApiController controller,
ModelType? modelType = null,
ApiSubController? subController = null,
object? parameters = null,
string[]? identifiers = null,
bool ignoreSsl = false)
{
EnsureValidSession();
var session = SessionManager.CurrentSession!;
var uri = BuildUri(session, controller, subController, identifiers);
using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler);
ConfigureClient(client, session);
var response = await SendRequestAsync(client, method, uri, parameters);
if (response == null)
{
return null;
}
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return null;
}
throw new HttpRequestException($"Request failed with status {response.StatusCode}");
}
if (string.IsNullOrEmpty(responseContent))
{
return null;
}
return ParseResponse(responseContent, modelType);
}
/// <summary>
/// Overload for backward compatibility using string method names.
/// </summary>
public static Task<object?> InvokeRequest(
string method,
ApiController controller,
ModelType? modelType = null,
ApiSubController? subController = null,
object? parameters = null,
string[]? identifiers = null,
bool ignoreSsl = false)
{
var httpMethod = method.ToUpperInvariant() switch
{
"GET" => HttpMethod.Get,
"POST" => HttpMethod.Post,
"PATCH" => new HttpMethod("PATCH"),
"DELETE" => HttpMethod.Delete,
"PUT" => HttpMethod.Put,
_ => throw new ArgumentException($"Unsupported HTTP method: {method}", nameof(method))
};
return InvokeRequestAsync(httpMethod, controller, modelType, subController, parameters, identifiers, ignoreSsl);
}
/// <summary>
/// Refreshes an expired session.
/// </summary>
public static async Task RefreshSessionAsync()
{
var session = SessionManager.CurrentSession;
if (session == null)
{
throw new InvalidOperationException("No session available!");
}
var status = SessionManager.GetSessionStatus();
if (status == SessionStatus.Valid)
{
// Just refresh the token
await InvokeRequestAsync(new HttpMethod("PATCH"), ApiController.User);
return;
}
if (status == SessionStatus.Expired && session.Credentials is PSCredential creds)
{
await SessionManager.CreateSessionWithCredentialsAsync(
session.URL,
session.AppID,
creds,
false
);
}
}
private static void EnsureValidSession()
{
var status = SessionManager.GetSessionStatus();
switch (status)
{
case SessionStatus.NoSession:
throw new InvalidOperationException("No session available!");
case SessionStatus.Expired:
RefreshSessionAsync().GetAwaiter().GetResult();
break;
}
}
private static string BuildUri(
Session session,
ApiController controller,
ApiSubController? subController,
string[]? identifiers)
{
var controllerName = GetControllerName(controller);
var uri = $"{session.URL}/api/{session.AppID}/{controllerName}";
if (subController.HasValue)
{
uri += $"/{GetSubControllerName(subController.Value)}";
}
if (identifiers is { Length: > 0 })
{
uri += $"/{string.Join("/", identifiers)}/";
}
return uri;
}
private static string GetControllerName(ApiController controller) => controller switch
{
ApiController.User => "user",
ApiController.Vlan => "vlan",
ApiController.Subnets => "subnets",
ApiController.Addresses => "addresses",
ApiController.Sections => "sections",
ApiController.Vrf => "vrf",
ApiController.L2Domains => "l2domains",
ApiController.Tools => "tools",
_ => throw new ArgumentOutOfRangeException(nameof(controller))
};
private static string GetSubControllerName(ApiSubController subController) => subController switch
{
ApiSubController.Nameservers => "nameservers",
ApiSubController.Tags => "tags",
ApiSubController.Devices => "devices",
ApiSubController.DeviceTypes => "device_types",
ApiSubController.Vlans => "vlans",
ApiSubController.Vrfs => "vrfs",
ApiSubController.ScanAgents => "scanagents",
ApiSubController.Locations => "locations",
ApiSubController.Nat => "nat",
ApiSubController.Racks => "racks",
_ => throw new ArgumentOutOfRangeException(nameof(subController))
};
private static void ConfigureClient(HttpClient client, Session session)
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
switch (session.AuthType)
{
case AuthType.Credentials:
client.DefaultRequestHeaders.Add("token", session.Token);
break;
case AuthType.Token:
client.DefaultRequestHeaders.Add("phpipam-token", session.Token);
break;
}
}
private static async Task<HttpResponseMessage?> SendRequestAsync(
HttpClient client,
HttpMethod method,
string uri,
object? parameters)
{
try
{
if (method == HttpMethod.Get)
{
return await client.GetAsync(uri);
}
if (method == HttpMethod.Delete)
{
return await client.DeleteAsync(uri);
}
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
if (method == HttpMethod.Post)
{
return await client.PostAsync(uri, content);
}
// PATCH, PUT, etc.
var request = new HttpRequestMessage(method, uri) { Content = content };
return await client.SendAsync(request);
}
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
{
return null;
}
}
private static object? ParseResponse(string responseContent, ModelType? modelType)
{
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (jsonResponse == null)
{
return null;
}
if (!modelType.HasValue)
{
return jsonResponse.data;
}
return ConvertToTypedObjects(jsonResponse, modelType.Value);
}
private static object? ConvertToTypedObjects(dynamic jsonResponse, ModelType modelType)
{
if (jsonResponse?.data == null)
{
return null;
}
var data = jsonResponse.data;
if (data is JArray array)
{
return array.Select(item => ConvertSingleObject(item, modelType)).ToList();
}
return ConvertSingleObject(data, modelType);
}
private static object? ConvertSingleObject(dynamic item, ModelType modelType)
{
if (item is not JObject jobject)
{
return null;
}
var customFields = ExtractCustomFields(jobject);
return modelType switch
{
ModelType.Address => CreateAddress(jobject, customFields),
ModelType.Vlan => CreateVlan(jobject, customFields),
ModelType.Subnetwork => CreateSubnetwork(jobject, customFields),
ModelType.Vrf => CreateVrf(jobject, customFields),
ModelType.Section => CreateSection(jobject),
ModelType.Tag => CreateTag(jobject),
ModelType.Nameserver => CreateNameserver(jobject),
ModelType.Domain => CreateDomain(jobject),
_ => jobject.ToObject<object>()
};
}
private static Dictionary<string, object>? ExtractCustomFields(JObject jobject)
{
var customFields = new Dictionary<string, object>();
foreach (var prop in jobject.Properties().Where(p => p.Name.StartsWith("custom_")))
{
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? string.Empty;
}
return customFields.Count > 0 ? customFields : null;
}
#region Model Factory Methods
private static Address CreateAddress(JObject obj, Dictionary<string, object>? customFields) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["subnetId"]?.ToObject<int>() ?? 0,
obj["ip"]?.ToString() ?? "",
obj["is_gateway"]?.ToObject<bool>() ?? false,
obj["description"]?.ToString() ?? "",
obj["hostname"]?.ToString() ?? "",
obj["mac"]?.ToString() ?? "",
obj["owner"]?.ToString() ?? "",
obj["tag"]?.ToObject<int>() ?? 0,
obj["deviceId"]?.ToObject<int>() ?? 0,
obj["location"]?.ToString() ?? "",
obj["port"]?.ToString() ?? "",
obj["note"]?.ToString() ?? "",
obj["lastSeen"]?.ToObject<DateTime?>(),
obj["excludePing"]?.ToObject<bool>() ?? false,
obj["PTRignore"]?.ToObject<bool>() ?? false,
obj["PTR"]?.ToObject<int>() ?? 0,
obj["firewallAddressObject"]?.ToString() ?? "",
obj["editDate"]?.ToObject<DateTime?>(),
obj["customer_id"]?.ToObject<int>() ?? 0,
customFields
);
private static Vlan CreateVlan(JObject obj, Dictionary<string, object>? customFields) => new(
obj["vlanId"]?.ToObject<int>() ?? 0,
obj["domainId"]?.ToObject<int>() ?? 0,
obj["name"]?.ToString() ?? "",
obj["number"]?.ToObject<int>() ?? 0,
obj["description"]?.ToString() ?? "",
obj["editDate"]?.ToObject<DateTime?>(),
obj["customer_id"]?.ToObject<int>() ?? 0,
customFields
);
private static Subnetwork CreateSubnetwork(JObject obj, Dictionary<string, object>? customFields) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["subnet"]?.ToString() ?? "",
obj["mask"]?.ToObject<int>() ?? 0,
obj["sectionId"]?.ToObject<int>() ?? 0,
obj["description"]?.ToString() ?? "",
obj["linked_subnet"]?.ToString() ?? "",
obj["firewallAddressObject"]?.ToString() ?? "",
obj["vrfId"]?.ToObject<int>() ?? 0,
obj["masterSubnetId"]?.ToObject<int>() ?? 0,
obj["allowRequests"]?.ToObject<bool>() ?? false,
obj["vlanId"]?.ToObject<int>() ?? 0,
obj["showName"]?.ToObject<bool>() ?? false,
obj["deviceId"]?.ToObject<int>() ?? 0,
obj["permissions"]?.ToString() ?? "",
obj["pingSubnet"]?.ToObject<bool>() ?? false,
obj["discoverSubnet"]?.ToObject<bool>() ?? false,
obj["resolveDNS"]?.ToObject<bool>() ?? false,
obj["DNSrecursive"]?.ToObject<bool>() ?? false,
obj["DNSrecords"]?.ToObject<bool>() ?? false,
obj["nameserverId"]?.ToObject<int>() ?? 0,
obj["scanAgent"]?.ToObject<bool>() ?? false,
obj["isFolder"]?.ToObject<bool>() ?? false,
obj["isFull"]?.ToObject<bool>() ?? false,
obj["isPool"]?.ToObject<bool>() ?? false,
obj["state"]?.ToObject<int>() ?? 0,
obj["threshold"]?.ToObject<int>() ?? 0,
obj["location"]?.ToObject<int>() ?? 0,
obj["editDate"]?.ToObject<DateTime?>(),
obj["lastScan"]?.ToObject<DateTime?>(),
obj["lastDiscovery"]?.ToObject<DateTime?>(),
obj["calculation"]?.ToObject<object>() ?? new object(),
customFields
);
private static Vrf CreateVrf(JObject obj, Dictionary<string, object>? customFields) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["name"]?.ToString() ?? "",
obj["rd"]?.ToString() ?? "",
obj["description"]?.ToString() ?? "",
obj["sections"]?.ToString() ?? "",
obj["editDate"]?.ToObject<DateTime?>(),
customFields
);
private static Section CreateSection(JObject obj) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["name"]?.ToString() ?? "",
obj["description"]?.ToString() ?? "",
obj["masterSection"]?.ToObject<int>() ?? 0,
obj["permissions"]?.ToString() ?? "",
obj["strictMode"]?.ToObject<bool>() ?? false,
obj["subnetOrdering"]?.ToString() ?? "",
obj["order"]?.ToObject<int>() ?? 0,
obj["editDate"]?.ToObject<DateTime?>(),
obj["showSubnet"]?.ToObject<bool>() ?? false,
obj["showVlan"]?.ToObject<bool>() ?? false,
obj["showVRF"]?.ToObject<bool>() ?? false,
obj["showSupernetOnly"]?.ToObject<bool>() ?? false,
obj["DNS"]?.ToObject<int>() ?? 0
);
private static Tag CreateTag(JObject obj) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["type"]?.ToString() ?? "",
obj["showtag"]?.ToObject<bool>() ?? false,
obj["bgcolor"]?.ToString() ?? "",
obj["fgcolor"]?.ToString() ?? "",
obj["compress"]?.ToString() ?? "",
obj["locked"]?.ToString() ?? "",
obj["updateTag"]?.ToObject<bool>() ?? false
);
private static Nameserver CreateNameserver(JObject obj) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["name"]?.ToString() ?? "",
obj["nameservers"]?.ToString() ?? "",
obj["description"]?.ToString() ?? "",
obj["permissions"]?.ToString() ?? "",
obj["editDate"]?.ToObject<DateTime?>()
);
private static Domain CreateDomain(JObject obj) => new(
obj["id"]?.ToObject<int>() ?? 0,
obj["name"]?.ToString() ?? "",
obj["description"]?.ToString() ?? "",
obj["sections"]?.ToString() ?? ""
);
#endregion
}

View File

@@ -0,0 +1,165 @@
namespace PS.IPAM.Helpers;
using System;
using System.Management.Automation;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
/// <summary>
/// Manages phpIPAM API sessions including creation, validation, and lifecycle.
/// </summary>
public static class SessionManager
{
private static Session? _currentSession;
/// <summary>
/// Gets or sets the current active session.
/// </summary>
public static Session? CurrentSession
{
get => _currentSession;
set => _currentSession = value;
}
/// <summary>
/// Tests the current session status.
/// </summary>
/// <returns>The session status indicating validity or issues.</returns>
public static SessionStatus GetSessionStatus()
{
if (_currentSession == null)
{
return SessionStatus.NoSession;
}
if (_currentSession.Expires == null)
{
return SessionStatus.Valid;
}
return _currentSession.Expires < DateTime.Now
? SessionStatus.Expired
: SessionStatus.Valid;
}
/// <summary>
/// Creates a new session using username/password credentials.
/// </summary>
/// <param name="url">The phpIPAM server URL.</param>
/// <param name="appId">The API application ID.</param>
/// <param name="credentials">The PowerShell credential object.</param>
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
/// <returns>The created session.</returns>
public static async Task<Session> CreateSessionWithCredentialsAsync(
string url,
string appId,
PSCredential credentials,
bool ignoreSsl = false)
{
var uri = $"{url}/api/{appId}/user";
var auth = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPasswordString(credentials)}"));
using var client = CreateHttpClient(ignoreSsl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
var response = await client.PostAsync(uri, null);
var content = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(content);
if (jsonResponse?.success != true)
{
throw new InvalidOperationException(
jsonResponse?.error?.ToString() ?? "Failed to create session");
}
var token = jsonResponse.data.token.ToString();
var expires = DateTime.Parse(jsonResponse.data.expires.ToString());
_currentSession = new Session(
AuthType.Credentials,
token,
appId,
url,
expires,
credentials
);
return _currentSession;
}
/// <summary>
/// Creates a new session using a static API token.
/// </summary>
/// <param name="url">The phpIPAM server URL.</param>
/// <param name="appId">The API application ID.</param>
/// <param name="token">The API token.</param>
/// <returns>The created session.</returns>
public static Session CreateSessionWithToken(string url, string appId, string token)
{
_currentSession = new Session(
AuthType.Token,
token,
appId,
url,
null,
null
);
return _currentSession;
}
/// <summary>
/// Closes the current session and clears session data.
/// </summary>
public static void CloseSession()
{
_currentSession = null;
}
/// <summary>
/// Creates an HttpClient with optional SSL bypass.
/// </summary>
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
/// <param name="handler">Optional custom message handler for testing.</param>
/// <returns>A configured HttpClient instance.</returns>
public static HttpClient CreateHttpClient(bool ignoreSsl = false, HttpMessageHandler? handler = null)
{
if (handler != null)
{
return new HttpClient(handler);
}
if (ignoreSsl)
{
var sslHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
return new HttpClient(sslHandler);
}
return new HttpClient();
}
/// <summary>
/// Extracts the plain text password from a PSCredential object.
/// </summary>
private static string GetPasswordString(PSCredential credential)
{
var ptr = Marshal.SecureStringToBSTR(credential.Password);
try
{
return Marshal.PtrToStringBSTR(ptr);
}
finally
{
Marshal.ZeroFreeBSTR(ptr);
}
}
}

View File

@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// This file provides the IsExternalInit type required for using C# 9 records
// in projects targeting .NET Standard 2.1 or earlier.
#if !NET5_0_OR_GREATER
namespace System.Runtime.CompilerServices
{
using System.ComponentModel;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
}
#endif

35
classlib/class/address.cs Normal file
View File

@@ -0,0 +1,35 @@
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents an IP address entry in phpIPAM.
/// </summary>
[Serializable]
public sealed record Address(
int Id,
int SubnetId,
string Ip,
bool IsGateway,
string Description,
string Hostname,
string MAC,
string Owner,
int TagId,
int DeviceId,
string Location,
string Port,
string Note,
DateTime? LastSeen,
bool ExcludePing,
bool PTRIgnore,
int PTR,
string FirewallAddressObject,
DateTime? EditDate,
int CustomerId,
Dictionary<string, object>? ExtendedData = null
)
{
public override string ToString() => Ip;
}

17
classlib/class/domain.cs Normal file
View File

@@ -0,0 +1,17 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents an L2 domain in phpIPAM.
/// </summary>
[Serializable]
public sealed record Domain(
int Id,
string Name,
string Description,
string Sections
)
{
public override string ToString() => Name;
}

View File

@@ -0,0 +1,35 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents a nameserver configuration in phpIPAM.
/// </summary>
[Serializable]
public sealed record Nameserver
{
public int Id { get; }
public string Name { get; }
public string[] NameServers { get; }
public string Description { get; }
public string Permissions { get; }
public DateTime? EditDate { get; }
public Nameserver(
int id,
string name,
string nameServers,
string description,
string permissions,
DateTime? editDate)
{
Id = id;
Name = name;
NameServers = nameServers?.Split(';', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
Description = description;
Permissions = permissions;
EditDate = editDate;
}
public override string ToString() => Name;
}

27
classlib/class/section.cs Normal file
View File

@@ -0,0 +1,27 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents a section in phpIPAM that organizes subnets.
/// </summary>
[Serializable]
public sealed record Section(
int Id,
string Name,
string Description,
int MasterSectionId,
string Permissions,
bool StrictMode,
string SubnetOrdering,
int Order,
DateTime? EditDate,
bool ShowSubnet,
bool ShowVlan,
bool ShowVRF,
bool ShowSupernetOnly,
int DNSId
)
{
public override string ToString() => Name;
}

27
classlib/class/session.cs Normal file
View File

@@ -0,0 +1,27 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents an authenticated session with phpIPAM API.
/// </summary>
[Serializable]
public sealed record Session(
AuthType AuthType,
string Token,
string AppID,
string URL,
DateTime? Expires,
object? Credentials
)
{
/// <summary>
/// Gets or sets the current authentication token.
/// </summary>
public string Token { get; set; } = Token;
/// <summary>
/// Gets or sets the token expiration time.
/// </summary>
public DateTime? Expires { get; set; } = Expires;
}

51
classlib/class/subnet.cs Normal file
View File

@@ -0,0 +1,51 @@
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents a subnet/network in phpIPAM.
/// </summary>
[Serializable]
public sealed record Subnetwork(
int Id,
string Subnet,
int Mask,
int SectionId,
string Description,
string LinkedSubnet,
string FirewallAddressObject,
int VrfId,
int MasterSubnetId,
bool AllowRequests,
int VlanId,
bool ShowName,
int DeviceId,
string Permissions,
bool PingSubnet,
bool DiscoverSubnet,
bool ResolveDNS,
bool DNSRecursive,
bool DNSRecords,
int NameserverId,
bool ScanAgent,
bool IsFolder,
bool IsFull,
bool IsPool,
int TagId,
int Threshold,
int LocationId,
DateTime? EditDate,
DateTime? LastScan,
DateTime? LastDiscovery,
object Calculation,
Dictionary<string, object>? ExtendedData = null
)
{
/// <summary>
/// Gets the subnet in CIDR notation (e.g., "192.168.1.0/24").
/// </summary>
public string CIDR => $"{Subnet}/{Mask}";
public override string ToString() => CIDR;
}

21
classlib/class/tag.cs Normal file
View File

@@ -0,0 +1,21 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents an address tag in phpIPAM.
/// </summary>
[Serializable]
public sealed record Tag(
int Id,
string Type,
bool ShowTag,
string BackgroundColor,
string ForegroundColor,
string Compress,
string Locked,
bool UpdateTag
)
{
public override string ToString() => Type;
}

27
classlib/class/vlan.cs Normal file
View File

@@ -0,0 +1,27 @@
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents a VLAN in phpIPAM.
/// </summary>
[Serializable]
public sealed record Vlan(
int Id,
int DomainId,
string Name,
int Number,
string Description,
DateTime? EditDate,
int CustomerId,
Dictionary<string, object>? ExtendedData = null
)
{
/// <summary>
/// Alias for Id to maintain API compatibility.
/// </summary>
public int VlanId => Id;
public override string ToString() => Number.ToString();
}

21
classlib/class/vrf.cs Normal file
View File

@@ -0,0 +1,21 @@
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents a VRF (Virtual Routing and Forwarding) instance in phpIPAM.
/// </summary>
[Serializable]
public sealed record Vrf(
int Id,
string Name,
string RouteDistinguisher,
string Description,
string Sections,
DateTime? EditDate,
Dictionary<string, object>? ExtendedData = null
)
{
public override string ToString() => Name;
}

18
classlib/classlib.csproj Normal file
View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AssemblyName>ps.ipam</AssemblyName>
<RootNamespace>PS.IPAM</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
namespace PS.IPAM;
/// <summary>
/// Represents the current status of an API session.
/// </summary>
public enum SessionStatus
{
/// <summary>
/// No session exists or no token is available.
/// </summary>
NoSession,
/// <summary>
/// The session token has expired.
/// </summary>
Expired,
/// <summary>
/// The session is valid and ready for use.
/// </summary>
Valid
}

20
classlib/enum/authType.cs Normal file
View File

@@ -0,0 +1,20 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Specifies the authentication method for phpIPAM API.
/// </summary>
[Serializable]
public enum AuthType
{
/// <summary>
/// Authentication using username and password credentials.
/// </summary>
Credentials,
/// <summary>
/// Authentication using a static API token.
/// </summary>
Token
}

View File

@@ -0,0 +1,19 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents the main API controllers in phpIPAM.
/// </summary>
[Serializable]
public enum ApiController
{
User,
Vlan,
Subnets,
Addresses,
Sections,
Vrf,
L2Domains,
Tools
}

View File

@@ -0,0 +1,21 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents sub-controllers/endpoints within main API controllers.
/// </summary>
[Serializable]
public enum ApiSubController
{
Nameservers,
Tags,
Devices,
DeviceTypes,
Vlans,
Vrfs,
ScanAgents,
Locations,
Nat,
Racks
}

19
classlib/enum/types.cs Normal file
View File

@@ -0,0 +1,19 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents the model types returned by the phpIPAM API.
/// </summary>
[Serializable]
public enum ModelType
{
Address,
Domain,
Section,
Subnetwork,
Nameserver,
Tag,
Vlan,
Vrf
}

View File

@@ -0,0 +1,21 @@
function ConvertTo-Hashtable {
[CmdletBinding()]
param (
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0
)]
[ValidateNotNullOrEmpty()]
[PSCustomObject]$InputObject
)
process {
$_hashtable = @{}
$InputObject | Get-Member -MemberType *Property | Where-Object {
$_hashtable.($_.name) = $CustomFields.($_.name)
}
Write-Output $_hashtable
}
}

View File

@@ -1,64 +0,0 @@
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)")
}
}

View File

@@ -0,0 +1,103 @@
function Invoke-Request {
[CmdletBinding()]
param (
[parameter(Mandatory=$true)]
[ValidateSet("GET","POST","PATCH","DELETE")]
[string]
$Method,
[parameter(Mandatory=$true)]
[PS.IPAM.controllers]
$Controller,
[parameter(Mandatory=$false)]
[PS.IPAM.types]
$Type,
[parameter(Mandatory=$false)]
[PS.IPAM.subcontrollers]
$SubController,
[Parameter(Mandatory=$false)]
[ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })]
$Params,
[Parameter(Mandatory=$false)]
[array]
$Identifiers
)
$_tokenStatus = Test-Session
if ($_tokenStatus -eq "NoToken") { throw "No session available!" }
if ($_tokenStatus -eq "Expired") { Update-Session }
$_uri = "$($script:psipamSession.URL)/api/$($script:psipamSession.AppID)/$Controller"
if ($null -ne $SubController) { $_uri += "/$SubController" }
if ($null -ne $Identifiers) { $_uri += "/$($Identifiers -join '/')/" }
$_headers = @{ }
switch ($script:psipamSession.AuthType) {
"Credentials" { $_headers.Add("token", $script:psipamSession.Token) }
"Token" { $_headers.Add("phpipam-token", $script:psipamSession.Token) }
}
$_arguments = @{
Method = $Method
Uri = $_uri
Headers = $_headers
ContentType = "application/json"
}
if ($Method -eq "POST" -or $Method -eq "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))
}
Write-Verbose -Message "Invoking web request to $($_uri), with method $($_arguments.Method), headers: $($_arguments.Headers)"
try {
$_response = Invoke-RestMethod @_arguments
}
catch [System.Net.WebException] {
switch ($_.Exception.Message) {
"The remote server returned an error: (404) Not Found." { Write-Verbose -Message "Not found." }
"The remote server returned an error: (400) Bad Request." { Write-Warning -Message "Bad Request." }
Default { Write-Verbose -Message "Error occured while requesting api" }
}
}
if (!$_response) { return $null }
if ($Type -is [PS.IPAM.types]) {
switch ($Type) {
"address" {
$_paramList = @("id","subnetId","ip","is_gateway","description","hostname","mac","owner","tag","deviceId","location","port","note","lastSeen","excludePing","PTRignore","PTR","firewallAddressObject","editDate","customer_id")
$_response.data | ForEach-Object {
New-Object -TypeName ([PS.IPAM.Address]) -ArgumentList (@(($_ | Select-Object $_paramList).psobject.properties.value) + ($_ | Select-Object -Property custom_* -ExcludeProperty 'custom_\*'))
}
}
"vlan" {
$_paramList = @("vlanId","domainId","name","number","description","editDate","customer_id","custom_fields")
$_response.data | ForEach-Object {
New-Object -TypeName ([PS.IPAM.Vlan]) -ArgumentList (@(($_ | Select-Object $_paramList).psobject.properties.value) + ($_ | Select-Object -Property custom_* -ExcludeProperty 'custom_\*'))
}
}
"subnetwork" {
$_response.data | ForEach-Object {
New-Object -TypeName ([PS.IPAM.Subnetwork]) -ArgumentList (@($_.psobject.properties.value[0..30]) + ($_ | Select-Object -Property custom_* -ExcludeProperty 'custom_\*'))
}
}
"vrf" {
$_response.data | ForEach-Object {
New-Object -TypeName ([PS.IPAM.Vrf]) -ArgumentList (@($_.psobject.properties.value[0..5]) + ($_ | Select-Object -Property custom_* -ExcludeProperty 'custom_\*'))
}
}
Default { $_response.data | ForEach-Object { New-Object -TypeName ("PS.IPAM.$Type") -ArgumentList $_.psobject.properties.value } }
}
} else {
$_response.data
}
}

View File

@@ -1,15 +0,0 @@
function Test-PSIPAMSession {
[CmdletBinding()]
param (
)
if ($script:ipamToken) {
if ($script:ipamExpires -lt (Get-Date)) {
return "Expired"
} else {
return "Valid"
}
} else {
return "NoToken"
}
}

View File

@@ -0,0 +1,19 @@
function Test-Session {
[CmdletBinding()]
param (
)
if ($script:psipamSession) {
if ($null -eq $script:psipamSession.Expires) {
return "Valid"
} else {
if ($script:psipamSession.Expires -lt (Get-Date)) {
return "Expired"
} else {
return "Valid"
}
}
} else {
return "NoToken"
}
}

View File

@@ -1,18 +0,0 @@
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
}
}

View File

@@ -0,0 +1,18 @@
function Update-Session {
[CmdletBinding()]
param (
[switch]
$Force
)
$_tokenStatus = Test-Session
if ($_tokenStatus -eq "NoToken") {
throw "No session available!"
}
if ($_tokenStatus -eq "Valid") {
return (Invoke-Request -Method [ps.ipam.methods]::PATCH -Controller [ps.ipam.controllers]::user).expires
}
if ($_tokenStatus -eq "Expired" -or $Force) {
New-Session -URL $script:psipamSession.URL -AppID $script:psipamSession.AppID -Credentials $script:psipamSession.Credentials
return
}
}

View File

@@ -0,0 +1,49 @@
function Assign-Tag {
<#
.SYNOPSIS
Assign tag to address.
.PARAMETER Tag
Tag object to assign.
#>
[CmdletBinding()]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Address]
$AddressObject,
[parameter(
Mandatory=$true,
Position=1
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Tag]
$Tag
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::addresses
Method = "PATCH"
}
$_id = $AddressObject.id
$_tagid = $Tag.id
$_identifiers = @($_id)
$_params.Add("Identifiers",$_identifiers)
$_body = @{ }
$_body.Add("tag", $_tagid)
$_params.Add("Params",$_body)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Assign-Tag

View File

@@ -0,0 +1,20 @@
function Close-Session {
<#
.SYNOPSIS
Removes (revokes) token.
#>
[CmdletBinding()]
param()
process {
$_params = @{
Controller = [PS.IPAM.controllers]::user
Method = "DELETE"
}
Invoke-Request @_params
$script:psipamSession = $null
}
}
Export-ModuleMember Close-Session

View File

@@ -0,0 +1,124 @@
function Get-Address {
<#
.SYNOPSIS
Returns address object.
#>
[CmdletBinding(DefaultParameterSetName="ByID")]
[OutputType([PS.IPAM.address])]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByIP"
)]
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=1,
ParameterSetName="BySubnetId"
)]
[ValidateNotNullOrEmpty()]
[ipaddress]
$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="ByHostBase"
)]
[ValidateNotNullOrEmpty()]
[string]
$HostBase,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByTag"
)]
[ValidateNotNullOrEmpty()]
[int]
$TagId,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="BySubnetId"
)]
[ValidateNotNullOrEmpty()]
[int]
$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 {
$_params = @{
Controller = [PS.IPAM.controllers]::addresses
Method = "GET"
Type = [PS.IPAM.types]::address
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers = @($id); break }
"ByIP" { $_identifiers = ("search",$IP); break }
"ByHostName" { $_identifiers = ("search_hostname",$HostName); break }
"ByHostBase" { $_identifiers = ("search_hostbase",$HostBase); break }
"ByTag" { $_identifiers = ("tags",$TagId,[PS.IPAM.controllers]::addresses); break }
"BySubnetId" {
if ($IP) {
$_identifiers = ($IP,$SubnetId)
} else {
$_params.Item("Controller") = [PS.IPAM.controllers]::subnets
$_identifiers = ($SubnetId,[PS.IPAM.controllers]::addresses)
}
break
}
"BySubnetCIDR" {
$_params.Item("Controller") = [PS.IPAM.controllers]::subnets
$_subnetId = (Get-Subnet -CIDR $SubnetCIDR).id
if (!$_subnetId) { throw "Cannot find subnet!" }
$_identifiers = ($_subnetId,[PS.IPAM.controllers]::addresses)
break
}
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-Address

View File

@@ -0,0 +1,56 @@
function Get-FirstFreeIP {
[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"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="BySubnetObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Subnetwork]
$SubnetObject
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::subnets
Method = "GET"
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_subnetId = $Id; break }
"ByCIDR" {
$_subnetId = (Get-Subnet -CIDR $CIDR).id
if (!$_subnetId) { throw "Cannot find subnet!" }
break
}
"BySubnetObject" { $_subnetId = $SubnetObject.Id; break }
}
$_identifiers = @($_subnetId,"first_free")
$_params.Add("Identifiers",$_identifiers)
return Invoke-Request @_params | Select-Object @{n="Ip";e={$_}}
}
}
Export-ModuleMember -Function Get-FirstFreeIP

View File

@@ -0,0 +1,29 @@
function Get-L2Domain {
[CmdletBinding(DefaultParameterSetName="ByID")]
[OutputType([PS.IPAM.Domain])]
param (
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::l2domains
Method = "GET"
Type = [PS.IPAM.types]::Domain
}
$_identifiers = @($Id)
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-L2Domain

View File

@@ -0,0 +1,31 @@
function Get-Nameserver {
[CmdletBinding(DefaultParameterSetName="NoParams")]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::tools
SubController = [PS.IPAM.subcontrollers]::nameservers
Method = "GET"
Type = [PS.IPAM.types]::nameserver
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_nameserverId = $Id; break }
}
$_identifiers = @($_nameserverId)
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-Nameserver

View File

@@ -1,33 +0,0 @@
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

View File

@@ -1,26 +0,0 @@
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

View File

@@ -1,29 +0,0 @@
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

View File

@@ -1,32 +0,0 @@
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

View File

@@ -1,129 +0,0 @@
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

View File

@@ -1,33 +0,0 @@
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

View File

@@ -1,26 +0,0 @@
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

View File

@@ -1,45 +0,0 @@
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

View File

@@ -1,26 +0,0 @@
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

View File

@@ -1,5 +1,5 @@
function Get-PSIPAMAddress { function Get-Permissions {
[CmdletBinding(DefaultParameterSetName="ByID")] [CmdletBinding()]
param ( param (
[parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")] [parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="ByID")]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateScript({ $_ -match "^\d+$" })]
@@ -41,10 +41,10 @@ function Get-PSIPAMAddress {
Method = "GET" Method = "GET"
} }
switch ($PSCmdlet.ParameterSetName) { switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers = @($id) } "ByID" { $_identifiers = @($id); break }
"ByIP" { $_identifiers = ("search",$IP) } "ByIP" { $_identifiers = ("search",$IP); break }
"ByHostName" { $_identifiers = ("search_hostname",$HostName) } "ByHostName" { $_identifiers = ("search_hostname",$HostName); break }
"ByTag" { $_identifiers = ("tags",$TagId,"addresses") } "ByTag" { $_identifiers = ("tags",$TagId,"addresses"); break }
"BySubnetId" { "BySubnetId" {
if ($IP) { if ($IP) {
$_identifiers = ($IP,$SubnetId) $_identifiers = ($IP,$SubnetId)
@@ -52,19 +52,21 @@ function Get-PSIPAMAddress {
$_params.Item("Controller") = "subnets" $_params.Item("Controller") = "subnets"
$_identifiers = ($SubnetId,"addresses") $_identifiers = ($SubnetId,"addresses")
} }
break
} }
"BySubnetCIDR" { "BySubnetCIDR" {
$_params.Item("Controller") = "subnets" $_params.Item("Controller") = "subnets"
$_subnetId = (Get-PSIPAMSubnet -CIDR $SubnetCIDR).id $_subnetId = (Get-Subnet -CIDR $SubnetCIDR).id
if (!$_subnetId) { throw "Cannot find subnet!" } if (!$_subnetId) { throw "Cannot find subnet!" }
$_identifiers = ($_subnetId,"addresses") $_identifiers = ($_subnetId,"addresses")
break
} }
} }
$_params.Add("Identifiers",$_identifiers) $_params.Add("Identifiers",$_identifiers)
Invoke-PSIPAMRequest @_params | ` Invoke-Request @_params | `
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru
} }
} }
Export-ModuleMember -Function Get-PSIPAMAddress Export-ModuleMember -Function Get-Permissions

View File

@@ -0,0 +1,39 @@
function Get-Section {
[CmdletBinding(DefaultParameterSetName="NoParams")]
[OutputType([PS.IPAM.Section])]
param (
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
Position=0,
ParameterSetName="ByName"
)]
[ValidateNotNullOrEmpty()]
[string]
$Name
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::sections
Method = "GET"
Type = [PS.IPAM.types]::Section
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers = @($Id); break }
"ByName" { $_identifiers = @($Name); break }
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember Get-Section

View File

@@ -0,0 +1,211 @@
function Get-Subnet {
[CmdletBinding(DefaultParameterSetName="NoParams")]
[OutputType([PS.IPAM.Subnetwork])]
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"
)]
[ValidateNotNullOrEmpty()]
[int]
$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"
)]
[ValidateNotNullOrEmpty()]
[int]
$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"
)]
[ValidateNotNullOrEmpty()]
[int]
$VrfId,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByVlanId"
)]
[ValidateNotNullOrEmpty()]
[int]
$VlanId,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByVlanNumber"
)]
[ValidateNotNullOrEmpty()]
[int]
$VlanNumber,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=1,
ParameterSetName="ByVlanNumber"
)]
[ValidateNotNullOrEmpty()]
[int]
$VlanDomainId,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=1,
ParameterSetName="ByID"
)]
[switch]
$Slaves,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=2,
ParameterSetName="ByID"
)]
[switch]
$Recurse
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::subnets
Method = "GET"
Type = [PS.IPAM.types]::Subnetwork
}
switch ($PSCmdlet.ParameterSetName) {
"ByCIDR" {
$_identifiers = @("cidr",$CIDR); break
}
"ByID" {
$_identifiers = @($Id)
if ($Slaves) {
if ($Recurse) {
$_identifiers += "slaves_recursive"
} else {
$_identifiers += "slaves"
}
}
break
}
"BySectionId" {
$_params.Item("Controller") = "sections"
$_sectionId = $SectionId
$_identifiers = @($_sectionId,"subnets")
break
}
"BySectionName" {
$_params.Item("Controller") = "sections"
$_sectionId = (Get-Section -Name $SectionName).id
if (!$_sectionId) { throw "Cannot find section!" }
$_identifiers = @($_sectionId,"subnets")
break
}
"ByVrfId" {
$_params.Item("Controller") = "vrf"
$_vrfId = $VrfId
$_identifiers = @($_vrfId,"subnets")
break
}
"ByVlanId" {
$_params.Item("Controller") = "vlan"
$_vlanId = $VlanId
if ($SectionId) { $_sectionId = $SectionId }
if ($SectionName){ $_sectionId = (Get-Section -Name $SectionName).id }
$_identifiers = @($_vlanId,"subnets")
if ($_sectionId) { $_identifiers += $_sectionId }
break
}
"ByVlanNumber" {
$_params.Item("Controller") = "vlan"
$_vlans = Get-Vlan -Number $VlanNumber
if ($VlanDomain) { $_vlans = $_vlans | Where-Object {$_.domainId -eq $VlanDomain} }
if ($SectionId) { $_sectionId = $SectionId }
if ($SectionName){ $_sectionId = (Get-Section -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 }
break
}
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember Get-Subnet

View File

@@ -0,0 +1,45 @@
function Get-SubnetUsage {
[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"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id
)
process {
$_params = @{
Controller = [ps.ipam.controllers]::subnets
Method = "GET"
}
switch ($PSCmdlet.ParameterSetName) {
"ByCIDR" {
$_subnetId = (Get-Subnet -CIDR $CIDR).id
if (!$_subnetId) { throw "Cannot find subnet!" }
break
}
"ByID" { $_subnetId = $Id; break }
}
$_identifiers = @($_subnetId,"usage")
$_params.Add("Identifiers",$_identifiers)
return Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-SubnetUsage

View File

@@ -0,0 +1,76 @@
function Get-Tag {
<#
.SYNOPSIS
Return address tag.
.DESCRIPTION
Return address tag by id, address object or subnetwork object. Or leave it empty at take all tags at once.
#>
[CmdletBinding(DefaultParameterSetName="NoParams")]
[OutputType([PS.IPAM.Tag])]
param (
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByAddressObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Address]
$AddressObject,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="BySubnetObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Subnetwork]
$SubnetObject
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::addresses
Method = "GET"
Type = [PS.IPAM.types]::tag
}
$_identifiers = @("tags")
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers += $Id; break }
"ByAddressObject" {
if ($AddressObject.TagId) {
$_identifiers += $AddressObject.TagId
} else {
return $null
}
break
}
"BySubnetObject" {
if ($SubnetObject.TagId) {
$_identifiers += $SubnetObject.TagId
} else {
return $null
}
break
}
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-Tag

View File

@@ -0,0 +1,101 @@
function Get-Vlan {
<#
.SYNOPSIS
Return vlan object.
.DESCRIPTION
Return vlan object by vlanId, vlan number, l2domainId (return all vlans in domain), subnet object or l2domain object
.PARAMETER Number
Vlan tag
#>
[CmdletBinding(DefaultParameterSetName="NoParams")]
[OutputType([PS.IPAM.Vlan])]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByNumber"
)]
[ValidateNotNullOrEmpty()]
[int]
$Number,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByL2Domain"
)]
[ValidateNotNullOrEmpty()]
[int]
$L2DomainId,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="BySubnetObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Subnetwork]
$SubnetObject,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByDomainObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Domain]
$DomainObject
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::vlan
Method = "GET"
Type = [PS.IPAM.types]::Vlan
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers = @($Id); break }
"ByNumber" { $_identifiers = @("search",$Number); break }
"ByL2Domain"{
$_params.Item("Controller") = [PS.IPAM.controllers]::l2domains
$_l2domainId = $L2DomainId
$_identifiers = @($_l2domainId,[PS.IPAM.subcontrollers]::vlans)
break
}
"BySubnetObject" {
if ($SubnetObject.VlanId) {
$_identifiers = @($SubnetObject.VlanId); break
} else {
return $null
}
}
"DomainObject" {
Get-Vlan -L2DomainId $DomainObject.Id
}
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-Vlan

View File

@@ -0,0 +1,38 @@
function Get-Vrf {
<#
.SYNOPSIS
Returns VRF object.
.DESCRIPTION
Returns VRF by id. Or leave it empty to get all VRFs.
#>
[CmdletBinding(DefaultParameterSetName="NoParams")]
[OutputType([PS.IPAM.Vrf])]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::vrf
Method = "GET"
Type = [PS.IPAM.types]::vrf
}
if ($Id) { $_identifiers = @($Id) }
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember -Function Get-Vrf

View File

@@ -1,21 +1,78 @@
function New-PSIPAMAddress { function New-Address {
[CmdletBinding()] <#
.SYNOPSIS
Creates address object.
.PARAMETER SubnetId
Id of subnet address belongs to.
.PARAMETER Ip
IP address.
.PARAMETER Gateway
Defines if address is presented as gateway.
.PARAMETER Description
Address description.
.PARAMETER Hostname
Address hostname.
.PARAMETER MAC
Mac address.
.PARAMETER Owner
Address owner.
.PARAMETER TagId
Id of subnet address belongs to.
.PARAMETER PTRIgnore
Controls if PTR should not be created.
.PARAMETER PTRId
Id of PowerDNS PTR record.
.PARAMETER Note
Note.
.PARAMETER ExcludePing
Exclude this address from status update scans (ping).
.PARAMETER DeviceId
Id of device address belongs to.
.PARAMETER Port
Port.
#>
[OutputType([PS.IPAM.address])]
[CmdletBinding(DefaultParameterSetName="BySubnetId")]
param ( param (
[parameter( [parameter(
Mandatory=$true, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of subnet address belongs to", Position=0,
Position=0)] ParameterSetName="BySubnetId"
[ValidateScript({ $_ -match "^\d+$" })] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$SubnetId, $SubnetId,
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="BySubnetObject"
)]
[ValidateNotNullOrEmpty()]
[ps.ipam.subnetwork]
$SubnetObject,
[parameter( [parameter(
Mandatory=$true, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="IP address",
Position=1)] Position=1)]
[ValidateScript({[ipaddress] $_ })] [ValidateScript({[ipaddress] $_ })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
@@ -25,7 +82,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Defines if address is presented as gateway",
Position=2)] Position=2)]
[switch] [switch]
$Gateway, $Gateway,
@@ -33,7 +89,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Address description",
Position=3)] Position=3)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
@@ -42,7 +97,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Address hostname",
Position=4)] Position=4)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
@@ -51,7 +105,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Mac address",
Position=5)] Position=5)]
[ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })] [ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
@@ -61,7 +114,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Address owner",
Position=6)] Position=6)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
@@ -70,17 +122,14 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of subnet address belongs to",
Position=7)] Position=7)]
[ValidateScript({ $_ -match "^\d+$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$TagId, $TagId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls if PTR should not be created",
Position=8)] Position=8)]
[switch] [switch]
$PTRIgnore, $PTRIgnore,
@@ -88,17 +137,14 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of PowerDNS PTR record",
Position=7)] Position=7)]
[ValidateScript({ $_ -match "^\d+$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$PTRId, $PTRId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Note",
Position=10)] Position=10)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
@@ -107,7 +153,6 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Exclude this address from status update scans (ping)",
Position=11)] Position=11)]
[switch] [switch]
$ExcludePing, $ExcludePing,
@@ -115,34 +160,46 @@ function New-PSIPAMAddress {
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of device address belongs to",
Position=12)] Position=12)]
[ValidateScript({ $_ -match "^\d+$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$DeviceId, $DeviceId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Port",
Position=13)] Position=13)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Port, $Port,
[parameter(
[parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=14
)]
[ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })]
$CustomFields $CustomFields
) )
process { process {
$_params = @{ $_params = @{
Controller = "addresses" Controller = [PS.IPAM.controllers]::addresses
Method = "POST" Method = "POST"
} }
switch ($PSCmdlet.ParameterSetName) {
"BySubnetId" {
$_subnetId = $SubnetId
break
}
"BySubnetObject" {
$_subnetId = $SubnetObject.id
break
}
}
$_body = @{ $_body = @{
subnetId = $SubnetId subnetId = $_subnetId
ip = $Ip ip = $Ip
} }
if ($Gateway) { $_body.Add("is_gateway", "1") } if ($Gateway) { $_body.Add("is_gateway", "1") }
@@ -160,10 +217,7 @@ function New-PSIPAMAddress {
if ($CustomFields) { if ($CustomFields) {
if ($CustomFields -is [PSCustomObject]) { if ($CustomFields -is [PSCustomObject]) {
$_customFields = @{}; $_customFields = ConvertTo-Hashtable -InputObject $CustomFields
$CustomFields | Get-Member -MemberType *Property | Where-Object {
$_customFields.($_.name) = $CustomFields.($_.name)
}
} else { $_customFields = $CustomFields } } else { $_customFields = $CustomFields }
$_body = $_body + $_customFields $_body = $_body + $_customFields
@@ -171,12 +225,11 @@ function New-PSIPAMAddress {
$_params.Add("Params",$_body) $_params.Add("Params",$_body)
try { $_result = Invoke-Request @_params
Invoke-PSIPAMRequest @_params
} if ($_result) {
finally { Get-Address -SubnetId $SubnetId -Ip $Ip
Get-PSIPAMAddress -SubnetId $SubnetId -Ip $Ip
} }
} }
} }
Export-ModuleMember -Function New-PSIPAMAddress Export-ModuleMember -Function New-Address

View File

@@ -1,4 +1,4 @@
function New-PSIPAMFirstFreeIP { function New-FirstFreeIP {
[CmdletBinding()] [CmdletBinding()]
param ( param (
[parameter( [parameter(
@@ -163,10 +163,10 @@ function New-PSIPAMFirstFreeIP {
$_params.Add("Params",$_body) $_params.Add("Params",$_body)
$_result = Invoke-PSIPAMRequest @_params $_result = Invoke-Request @_params
if ($_result) { if ($_result) {
Get-PSIPAMAddress -SubnetId $SubnetId -IP $_result Get-Address -SubnetId $SubnetId -IP $_result
} }
} }
} }
Export-ModuleMember -Function New-PSIPAMFirstFreeIP Export-ModuleMember -Function New-FirstFreeIP

View File

@@ -1,37 +0,0 @@
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

View File

@@ -0,0 +1,111 @@
function New-Session {
<#
.SYNOPSIS
Creates new session to phpIPAM instance.
.DESCRIPTION
Creates new session to phpIPAM instance by provided credentials or token.
.PARAMETER URL
Base URL of phpIPAM instance.
Example - http://ipam.example.com
.PARAMETER AppID
API id (specified in phpIPAM api settings)
.PARAMETER Token
User token foor authorization
.PARAMETER IgnoreSSL
Ignore SSL errors
.OUTPUTS
None. New-Session does not generate any output.
#>
[CmdletBinding(DefaultParameterSetName="Credentials")]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0
)]
[ValidateNotNullOrEmpty()]
[validatescript({$_.startswith("http")})]
[string]$URL,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=1
)]
[ValidateNotNullOrEmpty()]
[string]$AppID,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=2,
ParameterSetName="Credentials"
)]
[ValidateNotNullOrEmpty()]
[pscredential]$Credentials,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=2,
ParameterSetName="Token"
)]
[ValidateNotNullOrEmpty()]
[string]$Token,
[parameter(
Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=3
)]
[switch]$IgnoreSSL = $false
)
switch ($PSCmdlet.ParameterSetName) {
"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
if ($_response.success -ne $true) { return $_response.error }
$script:psipamSession = [PS.IPAM.Session]::new(
[ps.ipam.authType]::credentials,
$_response.data.token,
$AppID,
$URL,
(Get-Date $_response.data.expires),
$Credentials
)
break
}
"Token" {
$script:psipamSession = [PS.IPAM.Session]::new(
[ps.ipam.authType]::token,
$Token,
$AppID,
$URL,
$null,
$null
)
break
}
}
}
Export-ModuleMember -Function New-Session

View File

@@ -1,12 +1,69 @@
function New-PSIPAMSubnet { function New-Subnet {
<#
.SYNOPSIS
Creates new subnetwork object.
.PARAMETER CIDR
CIDR of subnet in dotted format (e.g 10.10.10.0/24).
.PARAMETER SectionId
Section identifier.
.PARAMETER Description
Subnet description.
.PARAMETER VlanId
Assigns subnet to VLAN.
.PARAMETER VrfId
Assigns subnet to VRF.
.PARAMETER MasterSubnetId
Master subnet id for nested subnet.
.PARAMETER NameserverId
Id of nameserver to attach to subnet.
.PARAMETER ShowName
Controls weather subnet is displayed as IP address or Name in subnets menu.
.PARAMETER DNSRecursive
Controls if PTR records should be created for subnet.
.PARAMETER DNSRecords
Controls weather hostname DNS records are displayed.
.PARAMETER AllowRequests
Controls if IP requests are allowed for subnet.
.PARAMETER ScanAgentId
Controls which scanagent to use for subnet (default id 1).
.PARAMETER DiscoverSubnet
Controls if new hosts should be discovered for new host scans.
.PARAMETER IsFull
Marks subnet as used.
.PARAMETER TagId
Assignes state (tag) to subnet (default: 1 Used).
.PARAMETER Threshold
Subnet threshold.
.PARAMETER LocationId
Location index.
#>
[OutputType([PS.IPAM.Subnetwork])]
[CmdletBinding()] [CmdletBinding()]
param ( param (
[parameter( [parameter(
Mandatory=$true, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="CIDR of subnet in dotted format (e.g 10.10.10.0/24)", Position=0
Position=0)] )]
[ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})] [ValidateScript({[ipaddress] $_.Split("/")[0] -and $_.Split("/")[1] -match "\d{1,2}"})]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
@@ -15,147 +72,154 @@ function New-PSIPAMSubnet {
Mandatory=$true, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Section identifier", Position=1
Position=1)] )]
[ValidateScript({ $_ -match "^\d+$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$SectionId, $SectionId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Subnet description", Position=2
Position=2)] )]
[ValidateNotNullOrEmpty()]
[string] [string]
$Description, $Description,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Assigns subnet to VLAN", Position=3
Position=3)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$VlanId, $VlanId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Assigns subnet to VRF", Position=4
Position=4)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$VrfId, $VrfId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Master subnet id for nested subnet", Position=5
Position=5)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$MasterSubnetId, $MasterSubnetId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of nameserver to attach to subnet", Position=6
Position=6)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$NameserverId, $NameserverId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls weather subnet is displayed as IP address or Name in subnets menu", Position=7
Position=7)] )]
[switch] [switch]
$ShowName, $ShowName,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls if PTR records should be created for subnet", Position=8
Position=8)] )]
[switch] [switch]
$DNSRecursive, $DNSRecursive,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls weather hostname DNS records are displayed", Position=9
Position=9)] )]
[switch] [switch]
$DNSRecords, $DNSRecords,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls if IP requests are allowed for subnet", Position=10
Position=10)] )]
[switch] [switch]
$AllowRequests, $AllowRequests,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls which scanagent to use for subnet (default id 1)", Position=11
Position=11)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$ScanAgentId, $ScanAgentId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls if new hosts should be discovered for new host scans", Position=12
Position=12)] )]
[switch] [switch]
$DiscoverSubnet, $DiscoverSubnet,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Marks subnet as used", Position=12
Position=12)] )]
[switch] [switch]
$IsFull, $IsFull,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Assignes state (tag) to subnet (default: 1 Used)", Position=12
Position=12)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$TagId, $TagId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Subnet threshold", Position=13
Position=13)] )]
[ValidateScript({ $_ -match "^\d+$" -and $_ -le 100 -and $_ -ge 1 })] [ValidateScript({ $_ -le 100 -and $_ -ge 1 })]
[ValidateNotNullOrEmpty()]
[int]
$Threshold, $Threshold,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Location index", Position=14
Position=14)] )]
[ValidateScript({ $_ -match "^\d+$" })] [ValidateNotNullOrEmpty()]
[string] [int]
$LocationId, $LocationId,
[parameter(
[parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=15
)]
[ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })]
[ValidateNotNullOrEmpty()]
$CustomFields $CustomFields
) )
process { process {
$_params = @{ $_params = @{
Controller = "subnets" Controller = [PS.IPAM.controllers]::subnets
Method = "POST" Method = "POST"
} }
@@ -182,10 +246,7 @@ function New-PSIPAMSubnet {
if ($CustomFields) { if ($CustomFields) {
if ($CustomFields -is [PSCustomObject]) { if ($CustomFields -is [PSCustomObject]) {
$_customFields = @{}; $_customFields = ConvertTo-Hashtable -InputObject $CustomFields
$CustomFields | Get-Member -MemberType *Property | Where-Object {
$_customFields.($_.name) = $CustomFields.($_.name)
}
} else { $_customFields = $CustomFields } } else { $_customFields = $CustomFields }
$_body = $_body + $_customFields $_body = $_body + $_customFields
@@ -193,10 +254,11 @@ function New-PSIPAMSubnet {
$_params.Add("Params",$_body) $_params.Add("Params",$_body)
$_result = Invoke-PSIPAMRequest @_params $_result = Invoke-Request @_params
if ($_result) { if ($_result) {
return Get-PSIPAMSubnet -CIDR $_result return Get-Subnet -CIDR $_result
} }
} }
} }
Export-ModuleMember -Function New-PSIPAMSubnet Export-ModuleMember -Function New-Subnet

View File

@@ -0,0 +1,39 @@
function Remove-Address {
[CmdletBinding(DefaultParameterSetName="ByID")]
param (
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
Position=0,
ParameterSetName="ByID"
)]
[ValidateNotNullOrEmpty()]
[int]
$Id,
[parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
ParameterSetName="ByAddressObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Address]
$AddressObject
)
process {
$_params = @{
Controller = [PS.IPAM.controllers]::addresses
Method = "DELETE"
}
switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_identifiers = @($Id); break }
"ByAddressObject" { $_identifiers = @($AddressObject.Id); break }
}
$_params.Add("Identifiers",$_identifiers)
Invoke-Request @_params
}
}
Export-ModuleMember Remove-Address

View File

@@ -1,35 +0,0 @@
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

View File

@@ -1,150 +1,171 @@
function Set-PSIPAMAddress { function Set-Address {
[CmdletBinding()] <#
.SYNOPSIS
Edit address object.
.PARAMETER Id
Id of subnet address belongs to.
.PARAMETER AddressObject
Address object to edit.
.PARAMETER Gateway
Defines if address is presented as gateway
.PARAMETER Description
Address description.
#>
[OutputType([PS.IPAM.address])]
[CmdletBinding(DefaultParameterSetName="ById")]
param ( param (
[parameter( [parameter(
Mandatory=$true, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of subnet address belongs to", Position=0,
Position=0)] ParameterSetName="ById"
[ValidateScript({ $_ -match "^\d+$" })] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$Id, $Id,
[parameter( [parameter(
Mandatory=$false, Mandatory=$true,
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Defines if address is presented as gateway", Position=0,
Position=1)] ParameterSetName="ByAddressObject"
)]
[ValidateNotNullOrEmpty()]
[PS.IPAM.Address]
$AddressObject,
[parameter(
Position=1
)]
[parameter(
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
ParameterSetName="ById"
)]
[bool] [bool]
$Gateway, $Gateway,
[parameter( [parameter(
Mandatory=$false, Position=2
)]
[parameter(
ValueFromPipeline=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Address description", ParameterSetName="ById"
Position=2)] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Description, $Description,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Address hostname", HelpMessage="Address hostname",
Position=3)] Position=3
)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Hostname, $Hostname,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Mac address", HelpMessage="Mac address",
Position=4)] Position=4
)]
[ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })] [ValidateScript({ $_.Replace(":","") -match "^$('([A-F0-9]{2})' * 6)$" })]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$MAC, $MAC,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Address owner", HelpMessage="Address owner",
Position=5)] Position=5
)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Owner, $Owner,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of subnet address belongs to", HelpMessage="Id of subnet address belongs to",
Position=6)] Position=6
[ValidateScript({ $_ -match "^\d+$" })] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$TagId, $TagId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Controls if PTR should not be created", HelpMessage="Controls if PTR should not be created",
Position=7)] Position=7
)]
[bool] [bool]
$PTRIgnore, $PTRIgnore,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of PowerDNS PTR record", HelpMessage="Id of PowerDNS PTR record",
Position=8)] Position=8
[ValidateScript({ $_ -match "^\d+$" })] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$PTRId, $PTRId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Note", HelpMessage="Note",
Position=9)] Position=9
)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Note, $Note,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Exclude this address from status update scans (ping)", HelpMessage="Exclude this address from status update scans (ping)",
Position=10)] Position=10
)]
[bool] [bool]
$ExcludePing, $ExcludePing,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Id of device address belongs to", HelpMessage="Id of device address belongs to",
Position=11)] Position=11
[ValidateScript({ $_ -match "^\d+$" })] )]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [int]
$DeviceId, $DeviceId,
[parameter( [parameter(
Mandatory=$false, Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Port", HelpMessage="Port",
Position=12)] Position=12
)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Port, $Port,
[parameter(Mandatory=$false)]
[parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })] [ValidateScript({ $_ -is [Hashtable] -or $_ -is [PSCustomObject] })]
$CustomFields $CustomFields
) )
process { process {
$_params = @{ $_params = @{
Controller = "addresses" Controller = [PS.IPAM.controllers]::addresses
Method = "PATCH" Method = "PATCH"
} }
$_identifiers = @($Id) switch ($PSCmdlet.ParameterSetName) {
"ByID" { $_id = $Id; break }
"ByAddressObject" { $_id = $AddressObject.id; break }
}
$_identifiers = @($_id)
$_params.Add("Identifiers",$_identifiers) $_params.Add("Identifiers",$_identifiers)
$_body = @{ } $_body = @{ }
if ($Gateway) { $_body.Add("is_gateway", [int]$Gateway) } if ($Gateway) { $_body.Add("is_gateway", $Gateway) }
if ($Description) { $_body.Add("description", $Description) } if ($Description) { $_body.Add("description", $Description) }
if ($Hostname) { $_body.Add("hostname", $Hostname) } if ($Hostname) { $_body.Add("hostname", $Hostname) }
if ($MAC) { $_body.Add("mac", $MAC) } if ($MAC) { $_body.Add("mac", $MAC) }
if ($Owner) { $_body.Add("owner", $Owner) } if ($Owner) { $_body.Add("owner", $Owner) }
if ($TagId) { $_body.Add("tag", $TagId) } if ($TagId) { $_body.Add("tag", $TagId) }
if ($PTRIgnore) { $_body.Add("PTRignore", [int]$PTRIgnore) } if ($PTRIgnore) { $_body.Add("PTRignore", $PTRIgnore) }
if ($PTRId) { $_body.add("PTR", $PTRId)} if ($PTRId) { $_body.add("PTR", $PTRId)}
if ($Note) { $_body.Add("note", $Note) } if ($Note) { $_body.Add("note", $Note) }
if ($ExcludePing) { $_body.Add("excludePing", [int]$ExcludePing) } if ($ExcludePing) { $_body.Add("excludePing", $ExcludePing) }
if ($DeviceId) { $_body.Add("deviceId", $DeviceId) } if ($DeviceId) { $_body.Add("deviceId", $DeviceId) }
if ($Port) { $_body.Add("port", $Port) } if ($Port) { $_body.Add("port", $Port) }
@@ -162,11 +183,11 @@ function Set-PSIPAMAddress {
$_params.Add("Params",$_body) $_params.Add("Params",$_body)
try { try {
Invoke-PSIPAMRequest @_params Invoke-Request @_params
} }
finally { finally {
Get-PSIPAMAddress -Id $Id Get-Address -id $_id
} }
} }
} }
Export-ModuleMember -Function Set-PSIPAMAddress Export-ModuleMember -Function Set-Address

Binary file not shown.

198
types/types.ps1xml Normal file
View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>ps.ipam.address</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Ip</Name>
<Name>Hostname</Name>
<Name>Description</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.tag</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Type</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.domain</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Name</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.section</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Name</Name>
<Name>MasterSectionId</Name>
<Name>StrictMode</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.vlan</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Name</Name>
<Name>DomainId</Name>
<Name>Number</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.vrf</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Name</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.subnetwork</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Subnet</Name>
<Name>Mask</Name>
<Name>SectionId</Name>
<Name>Description</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>ps.ipam.nameserver</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Name</Name>
<Name>NameServers</Name>
<Name>Description</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultKeyPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
</Members>
</Type>
</Types>