2026-01-19-nah9 #6

Merged
Arnike merged 6 commits from 2026-01-19-nah9 into main 2026-01-19 16:57:01 +03:00
46 changed files with 5270 additions and 29 deletions

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

@@ -0,0 +1,121 @@
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 dependencies
run: dotnet restore
- 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@v4
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@v4
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@v4
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, '-') }}

21
Jenkinsfile vendored
View File

@@ -1,21 +0,0 @@
pipeline {
agent {
label '.net7.0'
}
stages {
stage('Build classlib') {
steps {
sh '''cd classlib
dotnet build --no-incremental --force --configuration Release
dotnet publish -c Release'''
contentReplace(configs: [fileContentReplaceConfig(configs: [fileContentReplaceItemConfig(replace: 'ModuleVersion = \'2.0.$BUILD_NUMBER\'', search: 'ModuleVersion = \'2.0\'')], fileEncoding: 'UTF-8', filePath: 'ps.ipam.psd1')])
}
}
}
post {
success {
archiveArtifacts artifacts: 'classlib/bin/Release/netstandard2.1/publish/*.dll, *.psd1, *.psm1, LICENSE, **/*.ps1xml, **/*.ps1', followSymlinks: false, onlyIfSuccessful: true
}
}
}

View File

@@ -0,0 +1,323 @@
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");
}
// 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",
controllers.addresses,
types.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",
controllers.addresses,
types.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",
controllers.addresses,
types.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",
controllers.addresses,
types.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",
controllers.subnets,
types.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",
controllers.addresses,
types.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",
controllers.subnets,
types.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",
controllers.addresses,
types.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",
controllers.addresses,
types.Address
);
// Assert
await action.Should().ThrowAsync<Exception>().WithMessage("No session available!");
}
}

View File

@@ -0,0 +1,103 @@
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);
}
}

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", controllers.addresses);
// Assert
await action.Should().ThrowAsync<Exception>().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", controllers.addresses, types.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", controllers.subnets, null, subcontrollers.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", controllers.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", controllers.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", controllers.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", controllers.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", controllers.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", controllers.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", controllers.addresses, types.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", controllers.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", controllers.addresses, types.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", controllers.subnets, types.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", controllers.vlan, types.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", controllers.subnets, types.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.GetCIDR().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", controllers.sections, types.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", controllers.addresses, types.Address, null, null, new[] { "1" });
// Assert
var address = (Address)result!;
address.ExtendedData.Should().NotBeNull();
var extendedData = (Dictionary<string, object>)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", controllers.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", controllers.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", controllers.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", controllers.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", controllers.addresses, types.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 TestSession_WhenNoSession_ReturnsNoToken()
{
// Arrange
SessionManager.CurrentSession = null;
// Act
var result = SessionManager.TestSession();
// Assert
result.Should().Be("NoToken");
}
[Fact]
public void TestSession_WhenSessionWithNullExpires_ReturnsValid()
{
// Arrange
var session = new Session(AuthType.token, "test-token", "app", "https://test.com", null, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.TestSession();
// Assert
result.Should().Be("Valid");
}
[Fact]
public void TestSession_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.TestSession();
// Assert
result.Should().Be("Valid");
}
[Fact]
public void TestSession_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.TestSession();
// Assert
result.Should().Be("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,116 @@
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);
}
}

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,82 @@
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_ReturnsArrayWithEmptyString()
{
// Arrange
var nameServersString = "";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(1);
nameserver.NameServers[0].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();
}
}

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,92 @@
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 ToString_ReturnsDefaultObjectString()
{
// Arrange
var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null);
// Act
var result = session.ToString();
// Assert
result.Should().Contain("PS.IPAM.Session");
}
}

View File

@@ -0,0 +1,142 @@
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 GetCIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr)
{
// Arrange
var subnetwork = CreateSubnetwork(subnet, mask);
// Act
var result = subnetwork.GetCIDR();
// 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_EqualsGetCIDR()
{
// Arrange
var subnetwork = CreateSubnetwork("172.20.0.0", 12);
// Act & Assert
subnetwork.ToString().Should().Be(subnetwork.GetCIDR());
}
private static Subnetwork CreateSubnetwork(string subnet, int mask)
{
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,
new object(), null
);
}
}

View File

@@ -0,0 +1,77 @@
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.BGColor.Should().Be(bgColor);
tag.FGColor.Should().Be(fgColor);
tag.Compress.Should().BeTrue();
tag.Locked.Should().BeFalse();
tag.UpdateTag.Should().Be(updateTag);
}
[Theory]
[InlineData("Yes", true)]
[InlineData("No", false)]
[InlineData("", false)]
[InlineData("yes", false)] // Case sensitive
[InlineData("true", false)] // Only "Yes" is true
public void StringToBool_ConvertsCorrectly(string input, bool expected)
{
// The StringToBool is private, so we test through the constructor
// Using Compress field which uses StringToBool
var tag = new Tag(1, "Test", false, "", "", input, "No", false);
tag.Compress.Should().Be(expected);
}
[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 Locked_WithYes_IsTrue()
{
// Arrange & Act
var tag = new Tag(1, "Test", false, "", "", "No", "Yes", false);
// Assert
tag.Locked.Should().BeTrue();
}
}

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,43 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet("Assign", "Tag")]
public class AssignTagCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Mandatory = true,
Position = 1)]
[ValidateNotNullOrEmpty]
public Tag? Tag { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string> { AddressObject!.Id.ToString() };
var body = new Dictionary<string, object>
{
{ "tag", Tag!.Id }
};
RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, body, identifiers.ToArray())
.GetAwaiter().GetResult();
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "AssignTagError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,21 @@
namespace PS.IPAM.Cmdlets;
using System.Management.Automation;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Close, "Session")]
public class CloseSessionCmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
try
{
RequestHelper.InvokeRequest("DELETE", controllers.user, null, null, null, null)
.GetAwaiter().GetResult();
SessionManager.CloseSession();
}
catch (System.Exception ex)
{
WriteError(new ErrorRecord(ex, "CloseSessionError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,162 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Address", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Address))]
public class GetAddressCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP")]
[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")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostBase")]
[ValidateNotNullOrEmpty]
public string? HostBase { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag")]
[ValidateNotNullOrEmpty]
public int? TagId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId")]
[ValidateNotNullOrEmpty]
public int? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = controllers.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 = controllers.subnets;
identifiers.Add(SubnetId!.Value.ToString());
identifiers.Add("addresses");
}
break;
case "BySubnetCIDR":
controller = controllers.subnets;
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", SubnetCIDR! })
.GetAwaiter().GetResult();
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
}
var subnetObj = subnet as Subnetwork;
identifiers.Add(subnetObj!.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, types.Address, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetAddressError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,79 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "FirstFreeIP", DefaultParameterSetName = "ByID")]
public class GetFirstFreeIPCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR")]
[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")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
protected override void ProcessRecord()
{
try
{
int subnetId;
if (ParameterSetName == "ByCIDR")
{
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR! })
.GetAwaiter().GetResult();
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
}
var subnetObj = subnet as Subnetwork;
subnetId = subnetObj!.Id;
}
else if (ParameterSetName == "BySubnetObject")
{
subnetId = SubnetObject!.Id;
}
else
{
subnetId = Id!.Value;
}
var identifiers = new List<string> { subnetId.ToString(), "first_free" };
var result = RequestHelper.InvokeRequest("GET", controllers.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));
}
}
}

View File

@@ -0,0 +1,54 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "L2Domain", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Domain))]
public class GetL2DomainCmdlet : PSCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[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", controllers.l2domains, types.Domain, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetL2DomainError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,54 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Nameserver", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Nameserver))]
public class GetNameserverCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[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", controllers.tools, types.Nameserver, subcontrollers.nameservers, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetNameserverError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,148 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Permissions")]
public class GetPermissionsCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public string? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP")]
[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")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag")]
[ValidateNotNullOrEmpty]
public string? TagId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId")]
[ValidateNotNullOrEmpty]
public string? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = controllers.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 = controllers.subnets;
identifiers.Add(SubnetId!);
identifiers.Add("addresses");
}
break;
case "BySubnetCIDR":
controller = controllers.subnets;
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", SubnetCIDR! })
.GetAwaiter().GetResult();
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
}
var subnetObj = subnet as Subnetwork;
identifiers.Add(subnetObj!.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetPermissionsError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,65 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Section", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Section))]
public class GetSectionCmdlet : PSCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByName")]
[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", controllers.sections, types.Section, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSectionError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,261 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Subnet", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Subnetwork))]
public class GetSubnetCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR")]
[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")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[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]
public int? SectionId { get; set; }
[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]
public string? SectionName { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVrfId")]
[ValidateNotNullOrEmpty]
public int? VrfId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanId")]
[ValidateNotNullOrEmpty]
public int? VlanId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanNumber")]
[ValidateNotNullOrEmpty]
public int? VlanNumber { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByID")]
public SwitchParameter Slaves { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "ByID")]
public SwitchParameter Recurse { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByVlanNumber")]
[ValidateNotNullOrEmpty]
public int? VlanDomainId { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = controllers.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 = controllers.sections;
identifiers.Add(SectionId!.Value.ToString());
identifiers.Add("subnets");
break;
case "BySectionName":
controller = controllers.sections;
var section = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName! })
.GetAwaiter().GetResult();
if (section == null)
{
throw new Exception("Cannot find section!");
}
var sectionObj = section as Section;
identifiers.Add(sectionObj!.Id.ToString());
identifiers.Add("subnets");
break;
case "ByVrfId":
controller = controllers.vrf;
identifiers.Add(VrfId!.Value.ToString());
identifiers.Add("subnets");
break;
case "ByVlanId":
controller = controllers.vlan;
identifiers.Add(VlanId!.Value.ToString());
identifiers.Add("subnets");
if (SectionId.HasValue)
{
identifiers.Add(SectionId.Value.ToString());
}
else if (!string.IsNullOrEmpty(SectionName))
{
var section2 = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName })
.GetAwaiter().GetResult();
if (section2 != null)
{
var sectionObj2 = section2 as Section;
identifiers.Add(sectionObj2!.Id.ToString());
}
}
break;
case "ByVlanNumber":
controller = controllers.vlan;
var vlans = RequestHelper.InvokeRequest("GET", controllers.vlan, types.Vlan, null, null, new[] { "search", VlanNumber!.Value.ToString() })
.GetAwaiter().GetResult();
if (vlans == null)
{
throw new Exception("Cannot find Vlan!");
}
var vlanList = vlans as System.Collections.IEnumerable;
Vlan? foundVlan = null;
if (vlanList != null)
{
foreach (var v in vlanList)
{
if (v is Vlan vlan)
{
if (VlanDomainId.HasValue && vlan.DomainId != VlanDomainId.Value)
continue;
if (foundVlan != null)
{
throw new Exception($"More than one vLan with {VlanNumber} number is present!");
}
foundVlan = vlan;
}
}
}
if (foundVlan == null)
{
throw new Exception("Cannot find Vlan!");
}
identifiers.Add(foundVlan.Id.ToString());
identifiers.Add("subnets");
if (SectionId.HasValue)
{
identifiers.Add(SectionId.Value.ToString());
}
else if (!string.IsNullOrEmpty(SectionName))
{
var section3 = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName })
.GetAwaiter().GetResult();
if (section3 != null)
{
var sectionObj3 = section3 as Section;
identifiers.Add(sectionObj3!.Id.ToString());
}
}
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, types.Subnetwork, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSubnetError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,66 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "SubnetUsage", DefaultParameterSetName = "ByID")]
public class GetSubnetUsageCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR")]
[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")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
protected override void ProcessRecord()
{
try
{
int subnetId;
if (ParameterSetName == "ByCIDR")
{
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR! })
.GetAwaiter().GetResult();
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
}
var subnetObj = subnet as Subnetwork;
subnetId = subnetObj!.Id;
}
else
{
subnetId = Id!.Value;
}
var identifiers = new List<string> { subnetId.ToString(), "usage" };
var result = RequestHelper.InvokeRequest("GET", controllers.subnets, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
WriteObject(result);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSubnetUsageError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,98 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Tag", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Tag))]
public class GetTagCmdlet : PSCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
[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", controllers.addresses, types.Tag, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetTagError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,134 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Vlan", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vlan))]
public class GetVlanCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByNumber")]
[ValidateNotNullOrEmpty]
public int? Number { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByL2Domain")]
[ValidateNotNullOrEmpty]
public int? L2DomainId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByDomainObject")]
[ValidateNotNullOrEmpty]
public Domain? DomainObject { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = controllers.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 = controllers.l2domains;
identifiers.Add(L2DomainId!.Value.ToString());
identifiers.Add(subcontrollers.vlans.ToString());
break;
case "BySubnetObject":
if (SubnetObject != null && SubnetObject.VlanId > 0)
{
identifiers.Add(SubnetObject.VlanId.ToString());
}
else
{
return;
}
break;
case "ByDomainObject":
if (DomainObject != null)
{
var result = RequestHelper.InvokeRequest("GET", controllers.l2domains, types.Vlan, subcontrollers.vlans, null, new[] { DomainObject.Id.ToString() })
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
return;
}
var vlanResult = RequestHelper.InvokeRequest("GET", controller, types.Vlan, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
if (vlanResult != null)
{
if (vlanResult is System.Collections.IEnumerable enumerable && !(vlanResult is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(vlanResult);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetVlanError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,54 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Get, "Vrf", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vrf))]
public class GetVrfCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
[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", controllers.vrf, types.Vrf, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
if (result != null)
{
if (result is System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
WriteObject(item);
}
}
else
{
WriteObject(result);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetVrfError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,219 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.New, "Address", DefaultParameterSetName = "BySubnetId")]
[OutputType(typeof(Address))]
public class NewAddressCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId")]
[ValidateNotNullOrEmpty]
public int? SubnetId { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1)]
[ValidateNotNullOrEmpty]
public string IP { get; set; } = string.Empty;
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2)]
public SwitchParameter Gateway { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3)]
public string? Description { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 4)]
public string? Hostname { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 5)]
public string? MAC { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 6)]
public string? Owner { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 7)]
public int? TagId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 8)]
public SwitchParameter PTRIgnore { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 7)]
public int? PTRId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 10)]
public string? Note { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 11)]
public SwitchParameter ExcludePing { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 12)]
public int? DeviceId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 13)]
public string? Port { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 14)]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
int actualSubnetId;
if (ParameterSetName == "BySubnetObject")
{
actualSubnetId = SubnetObject!.Id;
}
else
{
actualSubnetId = SubnetId!.Value;
}
var body = new Dictionary<string, object>
{
{ "subnetId", actualSubnetId },
{ "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;
if (CustomFields != null)
{
var customDict = ConvertCustomFields(CustomFields);
foreach (var kvp in customDict)
{
body[kvp.Key] = kvp.Value;
}
}
var result = RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, body, null)
.GetAwaiter().GetResult();
if (result != null)
{
var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "search", IP })
.GetAwaiter().GetResult();
if (address != null)
{
WriteObject(address);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewAddressError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> ConvertCustomFields(object customFields)
{
var dict = new Dictionary<string, object>();
if (customFields is PSObject psobj)
{
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? new object();
}
}
else if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
return dict;
}
}

View File

@@ -0,0 +1,190 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.New, "FirstFreeIP")]
[OutputType(typeof(Address))]
public class NewFirstFreeIPCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
[ValidateNotNullOrEmpty]
public string SubnetId { get; set; } = string.Empty;
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2)]
public SwitchParameter Gateway { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3)]
public string? Description { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 4)]
public string? Hostname { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 5)]
public string? MAC { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 6)]
public string? Owner { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 7)]
public string? TagId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 8)]
public SwitchParameter PTRIgnore { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 9)]
public string? PTRId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 10)]
public string? Note { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 11)]
public SwitchParameter ExcludePing { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 12)]
public string? DeviceId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 13)]
public string? Port { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string> { "first_free" };
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;
if (CustomFields != null)
{
var customDict = ConvertCustomFields(CustomFields);
foreach (var kvp in customDict)
{
body[kvp.Key] = kvp.Value;
}
}
var result = RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, body, identifiers.ToArray())
.GetAwaiter().GetResult();
if (result != null)
{
var ip = result.ToString();
var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "search", ip })
.GetAwaiter().GetResult();
if (address != null)
{
WriteObject(address);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewFirstFreeIPError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> ConvertCustomFields(object customFields)
{
var dict = new Dictionary<string, object>();
if (customFields is PSObject psobj)
{
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? new object();
}
}
else if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
return dict;
}
}

View File

@@ -0,0 +1,81 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Management.Automation;
using System.Threading.Tasks;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.New, "Session", DefaultParameterSetName = "Credentials")]
public class NewSessionCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
[ValidateNotNullOrEmpty]
[ValidatePattern("^https?://")]
public string URL { get; set; } = string.Empty;
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1)]
[ValidateNotNullOrEmpty]
public string AppID { get; set; } = string.Empty;
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "Credentials")]
[ValidateNotNullOrEmpty]
public PSCredential? Credentials { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "Token")]
[ValidateNotNullOrEmpty]
public string? Token { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3)]
public SwitchParameter IgnoreSSL { get; set; }
protected override void ProcessRecord()
{
try
{
Session session;
if (ParameterSetName == "Credentials" && Credentials != null)
{
session = SessionManager.CreateSessionWithCredentials(
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");
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewSessionError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,231 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.New, "Subnet")]
[OutputType(typeof(Subnetwork))]
public class NewSubnetCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
[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)]
[ValidateNotNullOrEmpty]
public int SectionId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2)]
public string? Description { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 3)]
public int? VlanId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 4)]
public int? VrfId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 5)]
public int? MasterSubnetId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 6)]
public int? NameserverId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 7)]
public SwitchParameter ShowName { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 8)]
public SwitchParameter DNSRecursive { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 9)]
public SwitchParameter DNSRecords { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 10)]
public SwitchParameter AllowRequests { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 11)]
public int? ScanAgentId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 12)]
public SwitchParameter DiscoverSubnet { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 12)]
public SwitchParameter IsFull { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 12)]
public int? TagId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 13)]
[ValidateRange(1, 100)]
public int? Threshold { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 14)]
public int? LocationId { get; set; }
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 15)]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
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;
if (CustomFields != null)
{
var customDict = ConvertCustomFields(CustomFields);
foreach (var kvp in customDict)
{
body[kvp.Key] = kvp.Value;
}
}
var result = RequestHelper.InvokeRequest("POST", controllers.subnets, null, null, body, null)
.GetAwaiter().GetResult();
if (result != null)
{
// Get the created subnet
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR })
.GetAwaiter().GetResult();
if (subnet != null)
{
WriteObject(subnet);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "NewSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> ConvertCustomFields(object customFields)
{
var dict = new Dictionary<string, object>();
if (customFields is PSObject psobj)
{
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? new object();
}
}
else if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
return dict;
}
}

View File

@@ -0,0 +1,51 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Remove, "Address", DefaultParameterSetName = "ByID")]
public class RemoveAddressCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
protected override void ProcessRecord()
{
try
{
int addressId;
if (ParameterSetName == "ByID")
{
addressId = Id!.Value;
}
else
{
addressId = AddressObject!.Id;
}
var identifiers = new List<string> { addressId.ToString() };
RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "RemoveAddressError", ErrorCategory.InvalidOperation, null));
}
}
}

View File

@@ -0,0 +1,186 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Set, "Address", DefaultParameterSetName = "ById")]
[OutputType(typeof(Address))]
public class SetAddressCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ById")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Position = 1,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "ById")]
public bool? Gateway { get; set; }
[Parameter(
Position = 2,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "ById")]
public string? Description { get; set; }
[Parameter(
Mandatory = false,
Position = 3)]
public string? Hostname { get; set; }
[Parameter(
Mandatory = false,
Position = 4)]
public string? MAC { get; set; }
[Parameter(
Mandatory = false,
Position = 5)]
public string? Owner { get; set; }
[Parameter(
Mandatory = false,
Position = 6)]
public int? TagId { get; set; }
[Parameter(
Mandatory = false,
Position = 7)]
public bool? PTRIgnore { get; set; }
[Parameter(
Mandatory = false,
Position = 8)]
public int? PTRId { get; set; }
[Parameter(
Mandatory = false,
Position = 9)]
public string? Note { get; set; }
[Parameter(
Mandatory = false,
Position = 10)]
public bool? ExcludePing { get; set; }
[Parameter(
Mandatory = false,
Position = 11)]
public int? DeviceId { get; set; }
[Parameter(
Mandatory = false,
Position = 12)]
public string? Port { get; set; }
[Parameter(
Mandatory = false)]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
int addressId;
if (ParameterSetName == "ById")
{
addressId = Id!.Value;
}
else
{
addressId = AddressObject!.Id;
}
var identifiers = new List<string> { addressId.ToString() };
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;
if (CustomFields != null)
{
var customDict = ConvertCustomFields(CustomFields);
foreach (var kvp in customDict)
{
body[kvp.Key] = kvp.Value;
}
}
try
{
RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, body, identifiers.ToArray())
.GetAwaiter().GetResult();
}
finally
{
var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (address != null)
{
WriteObject(address);
}
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "SetAddressError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> ConvertCustomFields(object customFields)
{
var dict = new Dictionary<string, object>();
if (customFields is PSObject psobj)
{
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? new object();
}
}
else if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
return dict;
}
}

View File

@@ -0,0 +1,164 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
[Cmdlet(VerbsCommon.Set, "Subnet")]
[OutputType(typeof(Subnetwork))]
public class SetSubnetCmdlet : PSCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(
Mandatory = false)]
public string? Description { get; set; }
[Parameter(
Mandatory = false)]
public int? VlanId { get; set; }
[Parameter(
Mandatory = false)]
public int? VrfId { get; set; }
[Parameter(
Mandatory = false)]
public int? MasterSubnetId { get; set; }
[Parameter(
Mandatory = false)]
public int? NameserverId { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter ShowName { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter DNSRecursive { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter DNSRecords { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter AllowRequests { get; set; }
[Parameter(
Mandatory = false)]
public int? ScanAgentId { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter DiscoverSubnet { get; set; }
[Parameter(
Mandatory = false)]
public SwitchParameter IsFull { get; set; }
[Parameter(
Mandatory = false)]
public int? TagId { get; set; }
[Parameter(
Mandatory = false)]
[ValidateRange(1, 100)]
public int? Threshold { get; set; }
[Parameter(
Mandatory = false)]
public int? LocationId { get; set; }
[Parameter(
Mandatory = false)]
public object? CustomFields { get; set; }
protected override void ProcessRecord()
{
try
{
var identifiers = new List<string> { Id.ToString() };
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;
if (CustomFields != null)
{
var customDict = ConvertCustomFields(CustomFields);
foreach (var kvp in customDict)
{
body[kvp.Key] = kvp.Value;
}
}
RequestHelper.InvokeRequest("PATCH", controllers.subnets, null, null, body, identifiers.ToArray())
.GetAwaiter().GetResult();
var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (subnet != null)
{
WriteObject(subnet);
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "SetSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Dictionary<string, object> ConvertCustomFields(object customFields)
{
var dict = new Dictionary<string, object>();
if (customFields is PSObject psobj)
{
foreach (var prop in psobj.Properties)
{
dict[prop.Name] = prop.Value ?? new object();
}
}
else if (customFields is Dictionary<string, object> dictObj)
{
return dictObj;
}
return dict;
}
}

View File

@@ -0,0 +1,374 @@
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;
using PS.IPAM;
public static class RequestHelper
{
// Handler for testing - allows injecting a mock HTTP handler
public static HttpMessageHandler? TestHttpHandler { get; set; }
public static async Task<object?> InvokeRequest(
string method,
controllers controller,
types? type = null,
subcontrollers? subController = null,
object? parameters = null,
string[]? identifiers = null,
bool ignoreSsl = false)
{
var tokenStatus = SessionManager.TestSession();
if (tokenStatus == "NoToken")
{
throw new Exception("No session available!");
}
if (tokenStatus == "Expired")
{
await UpdateSession();
}
var session = SessionManager.CurrentSession;
if (session == null)
{
throw new Exception("No session available!");
}
var uri = $"{session.URL}/api/{session.AppID}/{controller}";
if (subController != null)
{
uri += $"/{subController}";
}
if (identifiers != null && identifiers.Length > 0)
{
uri += $"/{string.Join("/", identifiers)}/";
}
using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler);
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;
}
HttpResponseMessage? response = null;
try
{
if (method == "GET")
{
response = await client.GetAsync(uri);
}
else if (method == "POST")
{
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
response = await client.PostAsync(uri, content);
}
else if (method == "PATCH")
{
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(new HttpMethod("PATCH"), uri)
{
Content = content
};
response = await client.SendAsync(request);
}
else if (method == "DELETE")
{
response = await client.DeleteAsync(uri);
}
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;
}
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (jsonResponse == null)
{
return null;
}
if (type.HasValue)
{
return ConvertToTypedObjects(jsonResponse, type.Value);
}
return jsonResponse.data;
}
catch (HttpRequestException ex)
{
if (ex.Message.Contains("404"))
{
return null;
}
throw;
}
}
private static object? ConvertToTypedObjects(dynamic jsonResponse, types type)
{
if (jsonResponse?.data == null)
{
return null;
}
var data = jsonResponse.data;
if (data is JArray array)
{
return array.Select(item => ConvertSingleObject(item, type)).ToList();
}
else
{
return ConvertSingleObject(data, type);
}
}
private static object? ConvertSingleObject(dynamic item, types type)
{
var jobject = item as JObject;
if (jobject == null)
{
return null;
}
var customFields = new Dictionary<string, object>();
foreach (var prop in jobject.Properties())
{
if (prop.Name.StartsWith("custom_"))
{
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? new object();
}
}
switch (type)
{
case types.Address:
return CreateAddress(jobject, customFields);
case types.Vlan:
return CreateVlan(jobject, customFields);
case types.Subnetwork:
return CreateSubnetwork(jobject, customFields);
case types.Vrf:
return CreateVrf(jobject, customFields);
case types.Section:
return CreateSection(jobject);
case types.Tag:
return CreateTag(jobject);
case types.Nameserver:
return CreateNameserver(jobject);
case types.Domain:
return CreateDomain(jobject);
default:
return jobject.ToObject<object>();
}
}
private static Address CreateAddress(JObject jobject, Dictionary<string, object> customFields)
{
return new Address(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["subnetId"]?.ToObject<int>() ?? 0,
jobject["ip"]?.ToString() ?? "",
jobject["is_gateway"]?.ToObject<bool>() ?? false,
jobject["description"]?.ToString() ?? "",
jobject["hostname"]?.ToString() ?? "",
jobject["mac"]?.ToString() ?? "",
jobject["owner"]?.ToString() ?? "",
jobject["tag"]?.ToObject<int>() ?? 0,
jobject["deviceId"]?.ToObject<int>() ?? 0,
jobject["location"]?.ToString() ?? "",
jobject["port"]?.ToString() ?? "",
jobject["note"]?.ToString() ?? "",
jobject["lastSeen"]?.ToObject<DateTime?>(),
jobject["excludePing"]?.ToObject<bool>() ?? false,
jobject["PTRignore"]?.ToObject<bool>() ?? false,
jobject["PTR"]?.ToObject<int>() ?? 0,
jobject["firewallAddressObject"]?.ToString() ?? "",
jobject["editDate"]?.ToObject<DateTime?>(),
jobject["customer_id"]?.ToObject<int>() ?? 0,
customFields.Count > 0 ? customFields : null
);
}
private static Vlan CreateVlan(JObject jobject, Dictionary<string, object> customFields)
{
return new Vlan(
jobject["vlanId"]?.ToObject<int>() ?? 0,
jobject["domainId"]?.ToObject<int>() ?? 0,
jobject["name"]?.ToString() ?? "",
jobject["number"]?.ToObject<int>() ?? 0,
jobject["description"]?.ToString() ?? "",
jobject["editDate"]?.ToObject<DateTime?>(),
jobject["customer_id"]?.ToObject<int>() ?? 0,
customFields.Count > 0 ? customFields : null
);
}
private static Subnetwork CreateSubnetwork(JObject jobject, Dictionary<string, object> customFields)
{
var props = jobject.Properties().ToList();
return new Subnetwork(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["subnet"]?.ToString() ?? "",
jobject["mask"]?.ToObject<int>() ?? 0,
jobject["sectionId"]?.ToObject<int>() ?? 0,
jobject["description"]?.ToString() ?? "",
jobject["linked_subnet"]?.ToString() ?? "",
jobject["firewallAddressObject"]?.ToString() ?? "",
jobject["vrfId"]?.ToObject<int>() ?? 0,
jobject["masterSubnetId"]?.ToObject<int>() ?? 0,
jobject["allowRequests"]?.ToObject<bool>() ?? false,
jobject["vlanId"]?.ToObject<int>() ?? 0,
jobject["showName"]?.ToObject<bool>() ?? false,
jobject["deviceId"]?.ToObject<int>() ?? 0,
jobject["permissions"]?.ToString() ?? "",
jobject["pingSubnet"]?.ToObject<bool>() ?? false,
jobject["discoverSubnet"]?.ToObject<bool>() ?? false,
jobject["resolveDNS"]?.ToObject<bool>() ?? false,
jobject["DNSrecursive"]?.ToObject<bool>() ?? false,
jobject["DNSrecords"]?.ToObject<bool>() ?? false,
jobject["nameserverId"]?.ToObject<int>() ?? 0,
jobject["scanAgent"]?.ToObject<bool>() ?? false,
jobject["isFolder"]?.ToObject<bool>() ?? false,
jobject["isFull"]?.ToObject<bool>() ?? false,
jobject["isPool"]?.ToObject<bool>() ?? false,
jobject["state"]?.ToObject<int>() ?? 0,
jobject["threshold"]?.ToObject<int>() ?? 0,
jobject["location"]?.ToObject<int>() ?? 0,
jobject["editDate"]?.ToObject<DateTime?>(),
jobject["lastScan"]?.ToObject<DateTime?>(),
jobject["lastDiscovery"]?.ToObject<DateTime?>(),
jobject["calculation"]?.ToObject<object>() ?? new object(),
customFields.Count > 0 ? customFields : null
);
}
private static Vrf CreateVrf(JObject jobject, Dictionary<string, object> customFields)
{
return new Vrf(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["name"]?.ToString() ?? "",
jobject["rd"]?.ToString() ?? "",
jobject["description"]?.ToString() ?? "",
jobject["sections"]?.ToString() ?? "",
jobject["editDate"]?.ToObject<DateTime?>(),
customFields.Count > 0 ? customFields : null
);
}
private static Section CreateSection(JObject jobject)
{
return new Section(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["name"]?.ToString() ?? "",
jobject["description"]?.ToString() ?? "",
jobject["masterSection"]?.ToObject<int>() ?? 0,
jobject["permissions"]?.ToString() ?? "",
jobject["strictMode"]?.ToObject<bool>() ?? false,
jobject["subnetOrdering"]?.ToString() ?? "",
jobject["order"]?.ToObject<int>() ?? 0,
jobject["editDate"]?.ToObject<DateTime?>(),
jobject["showSubnet"]?.ToObject<bool>() ?? false,
jobject["showVlan"]?.ToObject<bool>() ?? false,
jobject["showVRF"]?.ToObject<bool>() ?? false,
jobject["showSupernetOnly"]?.ToObject<bool>() ?? false,
jobject["DNS"]?.ToObject<int>() ?? 0
);
}
private static Tag CreateTag(JObject jobject)
{
return new Tag(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["type"]?.ToString() ?? "",
jobject["showtag"]?.ToObject<bool>() ?? false,
jobject["bgcolor"]?.ToString() ?? "",
jobject["fgcolor"]?.ToString() ?? "",
jobject["compress"]?.ToString() ?? "",
jobject["locked"]?.ToString() ?? "",
jobject["updateTag"]?.ToObject<bool>() ?? false
);
}
private static Nameserver CreateNameserver(JObject jobject)
{
return new Nameserver(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["name"]?.ToString() ?? "",
jobject["nameservers"]?.ToString() ?? "",
jobject["description"]?.ToString() ?? "",
jobject["permissions"]?.ToString() ?? "",
jobject["editDate"]?.ToObject<DateTime?>()
);
}
private static Domain CreateDomain(JObject jobject)
{
return new Domain(
jobject["id"]?.ToObject<int>() ?? 0,
jobject["name"]?.ToString() ?? "",
jobject["description"]?.ToString() ?? "",
jobject["sections"]?.ToString() ?? ""
);
}
private static async Task UpdateSession()
{
var session = SessionManager.CurrentSession;
if (session == null)
{
throw new Exception("No session available!");
}
var tokenStatus = SessionManager.TestSession();
if (tokenStatus == "Valid")
{
// Just refresh the token
var result = await InvokeRequest("PATCH", controllers.user, null, null, null, null);
// Token refresh doesn't return expires in the same format, so we'll skip updating expires
return;
}
if (tokenStatus == "Expired" && session.Credentials is PSCredential creds)
{
await SessionManager.CreateSessionWithCredentials(
session.URL,
session.AppID,
creds,
false
);
}
}
}

View File

@@ -0,0 +1,131 @@
namespace PS.IPAM.Helpers;
using System;
using System.Management.Automation;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PS.IPAM;
public static class SessionManager
{
private static Session? _currentSession;
public static Session? CurrentSession
{
get => _currentSession;
set => _currentSession = value;
}
public static string TestSession()
{
if (_currentSession == null)
{
return "NoToken";
}
if (_currentSession.Expires == null)
{
return "Valid";
}
if (_currentSession.Expires < DateTime.Now)
{
return "Expired";
}
return "Valid";
}
public static async Task<Session> CreateSessionWithCredentials(
string url,
string appId,
PSCredential credentials,
bool ignoreSsl = false)
{
var uri = $"{url}/api/{appId}/user";
var auth = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPassword(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 Exception(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;
}
public static Session CreateSessionWithToken(
string url,
string appId,
string token)
{
_currentSession = new Session(
AuthType.token,
token,
appId,
url,
null,
null
);
return _currentSession;
}
public static void CloseSession()
{
_currentSession = null;
}
private static string GetPassword(PSCredential credential)
{
var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(credential.Password);
try
{
return System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
}
}
public static HttpClient CreateHttpClient(bool ignoreSsl = false, HttpMessageHandler? handler = null)
{
if (handler != null)
{
return new HttpClient(handler);
}
if (ignoreSsl)
{
var sslHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
return new HttpClient(sslHandler);
}
return new HttpClient();
}
}

View File

@@ -61,7 +61,7 @@ public class Address {
this.Location = location;
this.Port = port;
this.Note = note;
this.EditDate = lastSeen;
this.LastSeen = lastSeen;
this.ExcludePing = excludePing;
this.PTRignore = PTRignore;
this.PTR = PTR;

View File

@@ -1,21 +1,22 @@
namespace PS.IPAM;
using System;
using System.Management.Automation;
[Serializable]
public class Session {
public AuthType AuthType { get; }
public string Token { get; }
public string Token { get; set; }
public string AppID { get; }
public string URL { get; }
public DateTime? Expires { get; }
public Object? Credentials { get; }
public DateTime? Expires { get; set; }
public object? Credentials { get; }
public Session(
AuthType authType,
string token,
string appId,
string url,
DateTime? expires,
Object? credentials
object? credentials
) {
AuthType = authType;
Token = token;

View File

@@ -5,6 +5,7 @@ using System.Dynamic;
[Serializable]
public class Vlan : DynamicObject {
public int Id { get; }
public int VlanId { get; }
public int DomainId { get; }
public string Name { get; }
public int Number { get; }
@@ -23,6 +24,7 @@ public class Vlan : DynamicObject {
Object? custom_fields
) {
this.Id = vlanId;
this.VlanId = vlanId;
this.DomainId = domainId;
this.Name = name;
this.Number = number;

View File

@@ -5,6 +5,14 @@
<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

@@ -6,7 +6,7 @@
#
@{
RootModule = 'ps.ipam.psm1'
RootModule = 'ps.ipam.dll'
ModuleVersion = '2.0'
GUID = 'cd573493-4245-4073-a238-fab2251d78d0'
Author = 'Nikolay Tatarinov'
@@ -15,14 +15,14 @@
PowerShellVersion = '5.1'
RequiredAssemblies = @(
'classlib\bin\Release\netstandard2.1\publish\classlib.dll'
'ps.ipam.dll'
)
TypesToProcess = @(
'types\types.ps1xml'
)
FunctionsToExport = @(
CmdletsToExport = @(
'Assign-Tag',
'Close-Session',
'New-Session',
@@ -41,6 +41,7 @@
'New-Address',
'New-FirstFreeIP',
'Set-Address',
'Set-Subnet',
'Remove-Address'
)
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.