develop #7

Merged
Arnike merged 4 commits from develop into main 2026-01-19 17:35:20 +03:00
44 changed files with 1601 additions and 1905 deletions
Showing only changes of commit f56784f2aa - Show all commits

View File

@@ -109,6 +109,13 @@ public class GetAddressCmdletTests : IDisposable
cmdlet.SubnetCIDR.Should().Be("192.168.1.0/24");
}
[Fact]
public void GetAddressCmdlet_InheritsFromBaseCmdlet()
{
var cmdlet = new GetAddressCmdlet();
cmdlet.Should().BeAssignableTo<BaseCmdlet>();
}
// Test the underlying RequestHelper functionality that the cmdlet uses
[Fact]
@@ -129,8 +136,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "1" }
@@ -155,8 +162,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "42" }
@@ -177,8 +184,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "search", "192.168.1.50" }
@@ -199,8 +206,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "search_hostname", "myserver.example.com" }
@@ -221,8 +228,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
await RequestHelper.InvokeRequest(
"GET",
controllers.subnets,
types.Address,
ApiController.Subnets,
ModelType.Address,
null,
null,
new[] { "10", "addresses" }
@@ -243,8 +250,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "tags", "2", "addresses" }
@@ -269,8 +276,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
controllers.subnets,
types.Address,
ApiController.Subnets,
ModelType.Address,
null,
null,
new[] { "10", "addresses" }
@@ -294,8 +301,8 @@ public class GetAddressCmdletTests : IDisposable
// Act
var result = await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address,
ApiController.Addresses,
ModelType.Address,
null,
null,
new[] { "999" }
@@ -313,11 +320,11 @@ public class GetAddressCmdletTests : IDisposable
// Act
var action = async () => await RequestHelper.InvokeRequest(
"GET",
controllers.addresses,
types.Address
ApiController.Addresses,
ModelType.Address
);
// Assert
await action.Should().ThrowAsync<Exception>().WithMessage("No session available!");
await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("No session available!");
}
}

View File

@@ -93,11 +93,18 @@ public class NewSessionCmdletTests : IDisposable
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.AuthType.Should().Be(AuthType.Token);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
// Verify it was set as current session
SessionManager.CurrentSession.Should().BeSameAs(session);
}
[Fact]
public void NewSessionCmdlet_InheritsFromBaseCmdlet()
{
var cmdlet = new NewSessionCmdlet();
cmdlet.Should().BeAssignableTo<BaseCmdlet>();
}
}

View File

@@ -27,9 +27,9 @@ public class RequestHelperTests : IDisposable
_mockHandler?.ForceDispose();
}
private void SetupSession(AuthType authType = AuthType.token)
private void SetupSession(AuthType authType = AuthType.Token)
{
if (authType == AuthType.token)
if (authType == AuthType.Token)
{
SessionManager.CreateSessionWithToken("https://ipam.example.com", "testapp", "test-token");
}
@@ -37,7 +37,7 @@ public class RequestHelperTests : IDisposable
{
// For credentials auth, we need to set up a session manually with future expiry
var session = new Session(
AuthType.credentials,
AuthType.Credentials,
"cred-token",
"testapp",
"https://ipam.example.com",
@@ -61,10 +61,10 @@ public class RequestHelperTests : IDisposable
// Arrange - no session set up
// Act
var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses);
var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
await action.Should().ThrowAsync<Exception>().WithMessage("No session available!");
await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("No session available!");
}
[Fact]
@@ -76,7 +76,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{\"id\":1,\"ip\":\"192.168.1.1\"}");
// Act
await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
handler.LastRequest.Should().NotBeNull();
@@ -92,7 +92,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("[]");
// Act
await RequestHelper.InvokeRequest("GET", controllers.subnets, null, subcontrollers.tags, null, new[] { "1" });
await RequestHelper.InvokeRequest("GET", ApiController.Subnets, null, ApiSubController.Tags, null, new[] { "1" });
// Assert
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/subnets/tags/1/");
@@ -102,12 +102,12 @@ public class RequestHelperTests : IDisposable
public async Task InvokeRequest_WithTokenAuth_AddsPhpipamTokenHeader()
{
// Arrange
SetupSession(AuthType.token);
SetupSession(AuthType.Token);
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", controllers.addresses);
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.GetLastRequestHeader("phpipam-token").Should().Be("test-token");
@@ -117,12 +117,12 @@ public class RequestHelperTests : IDisposable
public async Task InvokeRequest_WithCredentialsAuth_AddsTokenHeader()
{
// Arrange
SetupSession(AuthType.credentials);
SetupSession(AuthType.Credentials);
var handler = SetupMockHandler();
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", controllers.addresses);
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.GetLastRequestHeader("token").Should().Be("cred-token");
@@ -137,7 +137,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", controllers.addresses);
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Get);
@@ -152,7 +152,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{\"id\":1}");
// Act
await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, new { ip = "10.0.0.1" });
await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, new { ip = "10.0.0.1" });
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Post);
@@ -167,7 +167,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{\"id\":1}");
// Act
await RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, new { description = "updated" }, new[] { "1" });
await RequestHelper.InvokeRequest("PATCH", ApiController.Addresses, null, null, new { description = "updated" }, new[] { "1" });
// Assert
handler.LastRequest!.Method.Should().Be(new HttpMethod("PATCH"));
@@ -182,7 +182,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, new[] { "1" });
await RequestHelper.InvokeRequest("DELETE", ApiController.Addresses, null, null, null, new[] { "1" });
// Assert
handler.LastRequest!.Method.Should().Be(HttpMethod.Delete);
@@ -197,7 +197,7 @@ public class RequestHelperTests : IDisposable
handler.WithNotFoundResponse();
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "999" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "999" });
// Assert
result.Should().BeNull();
@@ -212,7 +212,7 @@ public class RequestHelperTests : IDisposable
handler.WithErrorResponse(HttpStatusCode.InternalServerError, "Server error");
// Act
var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses);
var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
await action.Should().ThrowAsync<HttpRequestException>();
@@ -249,7 +249,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(addressJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Address>();
@@ -272,7 +272,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(addressArrayJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Address, null, null, new[] { "10", "addresses" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Address, null, null, new[] { "10", "addresses" });
// Assert
result.Should().BeAssignableTo<IEnumerable<object>>();
@@ -300,7 +300,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(vlanJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.vlan, types.Vlan, null, null, new[] { "100" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Vlan, ModelType.Vlan, null, null, new[] { "100" });
// Assert
result.Should().BeOfType<Vlan>();
@@ -351,14 +351,14 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(subnetJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "1" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Subnetwork, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Subnetwork>();
var subnet = (Subnetwork)result!;
subnet.Subnet.Should().Be("192.168.1.0");
subnet.Mask.Should().Be(24);
subnet.GetCIDR().Should().Be("192.168.1.0/24");
subnet.CIDR.Should().Be("192.168.1.0/24");
}
[Fact]
@@ -386,7 +386,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(sectionJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { "1" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Sections, ModelType.Section, null, null, new[] { "1" });
// Assert
result.Should().BeOfType<Section>();
@@ -411,12 +411,12 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(addressJson);
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" });
// Assert
var address = (Address)result!;
address.ExtendedData.Should().NotBeNull();
var extendedData = (Dictionary<string, object>)address.ExtendedData!;
var extendedData = address.ExtendedData!;
extendedData.Should().ContainKey("custom_environment");
extendedData.Should().ContainKey("custom_owner");
}
@@ -430,7 +430,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse(@"{""some"": ""data""}");
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.tools);
var result = await RequestHelper.InvokeRequest("GET", ApiController.Tools);
// Assert
result.Should().NotBeNull();
@@ -447,7 +447,7 @@ public class RequestHelperTests : IDisposable
var parameters = new { ip = "10.0.0.1", subnetId = 5, description = "New address" };
// Act
await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, parameters);
await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, parameters);
// Assert
handler.LastRequest!.Content.Should().NotBeNull();
@@ -465,7 +465,7 @@ public class RequestHelperTests : IDisposable
handler.WithSuccessResponse("{}");
// Act
await RequestHelper.InvokeRequest("GET", controllers.addresses);
await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
handler.LastRequest!.Headers.Accept.Should().Contain(h => h.MediaType == "application/json");
@@ -480,7 +480,7 @@ public class RequestHelperTests : IDisposable
handler.WithResponse(HttpStatusCode.OK, "", "application/json");
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses);
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses);
// Assert
result.Should().BeNull();
@@ -495,7 +495,7 @@ public class RequestHelperTests : IDisposable
handler.WithJsonResponse(@"{""code"": 200, ""success"": true, ""data"": null}");
// Act
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address);
var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address);
// Assert
result.Should().BeNull();

View File

@@ -21,60 +21,60 @@ public class SessionManagerTests : IDisposable
}
[Fact]
public void TestSession_WhenNoSession_ReturnsNoToken()
public void GetSessionStatus_WhenNoSession_ReturnsNoSession()
{
// Arrange
SessionManager.CurrentSession = null;
// Act
var result = SessionManager.TestSession();
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be("NoToken");
result.Should().Be(SessionStatus.NoSession);
}
[Fact]
public void TestSession_WhenSessionWithNullExpires_ReturnsValid()
public void GetSessionStatus_WhenSessionWithNullExpires_ReturnsValid()
{
// Arrange
var session = new Session(AuthType.token, "test-token", "app", "https://test.com", null, null);
var session = new Session(AuthType.Token, "test-token", "app", "https://test.com", null, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.TestSession();
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be("Valid");
result.Should().Be(SessionStatus.Valid);
}
[Fact]
public void TestSession_WhenSessionNotExpired_ReturnsValid()
public void GetSessionStatus_WhenSessionNotExpired_ReturnsValid()
{
// Arrange
var futureExpiry = DateTime.Now.AddHours(1);
var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", futureExpiry, null);
var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", futureExpiry, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.TestSession();
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be("Valid");
result.Should().Be(SessionStatus.Valid);
}
[Fact]
public void TestSession_WhenSessionExpired_ReturnsExpired()
public void GetSessionStatus_WhenSessionExpired_ReturnsExpired()
{
// Arrange
var pastExpiry = DateTime.Now.AddHours(-1);
var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", pastExpiry, null);
var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", pastExpiry, null);
SessionManager.CurrentSession = session;
// Act
var result = SessionManager.TestSession();
var result = SessionManager.GetSessionStatus();
// Assert
result.Should().Be("Expired");
result.Should().Be(SessionStatus.Expired);
}
[Fact]
@@ -93,7 +93,7 @@ public class SessionManagerTests : IDisposable
session.URL.Should().Be(url);
session.AppID.Should().Be(appId);
session.Token.Should().Be(token);
session.AuthType.Should().Be(AuthType.token);
session.AuthType.Should().Be(AuthType.Token);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
}
@@ -144,7 +144,7 @@ public class SessionManagerTests : IDisposable
public void CurrentSession_CanBeSetDirectly()
{
// Arrange
var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null);
var session = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
// Act
SessionManager.CurrentSession = session;

View File

@@ -55,7 +55,7 @@ public class AddressTests
address.Note.Should().Be(note);
address.LastSeen.Should().Be(lastSeen);
address.ExcludePing.Should().Be(excludePing);
address.PTRignore.Should().Be(ptrIgnore);
address.PTRIgnore.Should().Be(ptrIgnore);
address.PTR.Should().Be(ptr);
address.FirewallAddressObject.Should().Be(firewallObject);
address.EditDate.Should().Be(editDate);
@@ -113,4 +113,24 @@ public class AddressTests
// Act & Assert
address.ToString().Should().Be(expectedIp);
}
[Fact]
public void Record_Equality_WorksCorrectly()
{
// Arrange
var address1 = new Address(
1, 10, "192.168.1.1", false, "Test", "host", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
var address2 = new Address(
1, 10, "192.168.1.1", false, "Test", "host", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Assert
address1.Should().Be(address2);
}
}

View File

@@ -57,7 +57,7 @@ public class NameserverTests
}
[Fact]
public void Constructor_WithEmptyNameservers_ReturnsArrayWithEmptyString()
public void Constructor_WithEmptyNameservers_ReturnsEmptyArray()
{
// Arrange
var nameServersString = "";
@@ -65,9 +65,8 @@ public class NameserverTests
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(1);
nameserver.NameServers[0].Should().BeEmpty();
// Assert - Empty entries are excluded
nameserver.NameServers.Should().BeEmpty();
}
[Fact]
@@ -79,4 +78,27 @@ public class NameserverTests
// Assert
nameserver.EditDate.Should().BeNull();
}
[Fact]
public void Constructor_WithSemicolonOnlyString_ReturnsEmptyArray()
{
// Arrange - edge case with just semicolons
var nameServersString = ";;;";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().BeEmpty();
}
[Fact]
public void ToString_ReturnsName()
{
// Arrange
var nameserver = new Nameserver(1, "Google DNS", "8.8.8.8", "", "", null);
// Act & Assert
nameserver.ToString().Should().Be("Google DNS");
}
}

View File

@@ -10,7 +10,7 @@ public class SessionTests
public void Constructor_WithCredentialsAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.credentials;
var authType = AuthType.Credentials;
var token = "test-token-123";
var appId = "myApp";
var url = "https://ipam.example.com";
@@ -21,7 +21,7 @@ public class SessionTests
var session = new Session(authType, token, appId, url, expires, credentials);
// Assert
session.AuthType.Should().Be(AuthType.credentials);
session.AuthType.Should().Be(AuthType.Credentials);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
@@ -33,7 +33,7 @@ public class SessionTests
public void Constructor_WithTokenAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.token;
var authType = AuthType.Token;
var token = "static-api-token";
var appId = "apiApp";
var url = "https://ipam.test.com";
@@ -42,7 +42,7 @@ public class SessionTests
var session = new Session(authType, token, appId, url, null, null);
// Assert
session.AuthType.Should().Be(AuthType.token);
session.AuthType.Should().Be(AuthType.Token);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
@@ -54,7 +54,7 @@ public class SessionTests
public void Token_CanBeModified()
{
// Arrange
var session = new Session(AuthType.credentials, "old-token", "app", "https://test.com", null, null);
var session = new Session(AuthType.Credentials, "old-token", "app", "https://test.com", null, null);
// Act
session.Token = "new-token";
@@ -67,7 +67,7 @@ public class SessionTests
public void Expires_CanBeModified()
{
// Arrange
var session = new Session(AuthType.credentials, "token", "app", "https://test.com", null, null);
var session = new Session(AuthType.Credentials, "token", "app", "https://test.com", null, null);
var newExpiry = new DateTime(2027, 1, 1);
// Act
@@ -78,15 +78,13 @@ public class SessionTests
}
[Fact]
public void ToString_ReturnsDefaultObjectString()
public void Record_Equality_WorksForSameValues()
{
// Arrange
var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null);
var session1 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
var session2 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null);
// Act
var result = session.ToString();
// Assert
result.Should().Contain("PS.IPAM.Session");
// Assert - Records use value equality
session1.Should().Be(session2);
}
}

View File

@@ -93,13 +93,13 @@ public class SubnetworkTests
[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)
public void CIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr)
{
// Arrange
var subnetwork = CreateSubnetwork(subnet, mask);
// Act
var result = subnetwork.GetCIDR();
var result = subnetwork.CIDR;
// Assert
result.Should().Be(expectedCidr);
@@ -119,16 +119,31 @@ public class SubnetworkTests
}
[Fact]
public void ToString_EqualsGetCIDR()
public void ToString_EqualsCIDRProperty()
{
// Arrange
var subnetwork = CreateSubnetwork("172.20.0.0", 12);
// Act & Assert
subnetwork.ToString().Should().Be(subnetwork.GetCIDR());
subnetwork.ToString().Should().Be(subnetwork.CIDR);
}
private static Subnetwork CreateSubnetwork(string subnet, int mask)
[Fact]
public void Record_Equality_WorksCorrectly()
{
// Arrange - use same calculation object for equality comparison
var calculation = new { maxhosts = 254 };
var subnet1 = CreateSubnetwork("192.168.1.0", 24, calculation);
var subnet2 = CreateSubnetwork("192.168.1.0", 24, calculation);
// Assert - records use value equality (except for the Calculation object reference)
subnet1.Id.Should().Be(subnet2.Id);
subnet1.Subnet.Should().Be(subnet2.Subnet);
subnet1.Mask.Should().Be(subnet2.Mask);
subnet1.CIDR.Should().Be(subnet2.CIDR);
}
private static Subnetwork CreateSubnetwork(string subnet, int mask, object? calculation = null)
{
return new Subnetwork(
1, subnet, mask, 1, "", "", "",
@@ -136,7 +151,7 @@ public class SubnetworkTests
"", false, false, false, false,
false, 0, false, false, false, false,
0, 0, 0, null, null, null,
new object(), null
calculation ?? new object(), null
);
}
}

View File

@@ -26,28 +26,13 @@ public class TagTests
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.BackgroundColor.Should().Be(bgColor);
tag.ForegroundColor.Should().Be(fgColor);
tag.Compress.Should().Be(compress);
tag.Locked.Should().Be(locked);
tag.UpdateTag.Should().Be(updateTag);
}
[Theory]
[InlineData("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")]
@@ -66,12 +51,13 @@ public class TagTests
}
[Fact]
public void Locked_WithYes_IsTrue()
public void Record_Equality_WorksCorrectly()
{
// Arrange & Act
var tag = new Tag(1, "Test", false, "", "", "No", "Yes", false);
// Arrange
var tag1 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false);
var tag2 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false);
// Assert
tag.Locked.Should().BeTrue();
tag1.Should().Be(tag2);
}
}

View File

@@ -1,24 +1,30 @@
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
/// <summary>
/// Assigns a tag to an address in phpIPAM.
/// </summary>
[Cmdlet("Assign", "Tag", SupportsShouldProcess = true)]
[OutputType(typeof(Address))]
public class AssignTagCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0)]
Position = 0,
HelpMessage = "The address to assign the tag to.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
[Parameter(
Mandatory = true,
Position = 1)]
Position = 1,
HelpMessage = "The tag to assign.")]
[ValidateNotNullOrEmpty]
public Tag? Tag { get; set; }
@@ -26,14 +32,25 @@ public class AssignTagCmdlet : PSCmdlet
{
try
{
var identifiers = new List<string> { AddressObject!.Id.ToString() };
var identifiers = new[] { AddressObject!.Id.ToString() };
var body = new Dictionary<string, object>
{
{ "tag", Tag!.Id }
["tag"] = Tag!.Id
};
RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, body, identifiers.ToArray())
.GetAwaiter().GetResult();
if (ShouldProcess($"Address {AddressObject.Ip}", $"Assign tag '{Tag.Type}'"))
{
RequestHelper.InvokeRequest(
"PATCH", ApiController.Addresses, null, null, body, identifiers
).GetAwaiter().GetResult();
// Return the updated address
var address = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.Address, null, null, identifiers
).GetAwaiter().GetResult();
WriteResult(address);
}
}
catch (Exception ex)
{

View File

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

View File

@@ -1,21 +1,25 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves IP address entries from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Address", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Address))]
public class GetAddressCmdlet : PSCmdlet
public class GetAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The address ID to retrieve.")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
@@ -24,7 +28,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP")]
ParameterSetName = "ByIP",
HelpMessage = "The IP address to search for.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
@@ -39,7 +44,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostName")]
ParameterSetName = "ByHostName",
HelpMessage = "The hostname to search for.")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
@@ -48,7 +54,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostBase")]
ParameterSetName = "ByHostBase",
HelpMessage = "The hostname base pattern to search for.")]
[ValidateNotNullOrEmpty]
public string? HostBase { get; set; }
@@ -57,7 +64,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag")]
ParameterSetName = "ByTag",
HelpMessage = "The tag ID to filter addresses by.")]
[ValidateNotNullOrEmpty]
public int? TagId { get; set; }
@@ -66,7 +74,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId")]
ParameterSetName = "BySubnetId",
HelpMessage = "The subnet ID to get addresses from.")]
[ValidateNotNullOrEmpty]
public int? SubnetId { get; set; }
@@ -75,7 +84,8 @@ public class GetAddressCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR")]
ParameterSetName = "BySubnetCIDR",
HelpMessage = "The subnet in CIDR notation to get addresses from.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
@@ -84,7 +94,7 @@ public class GetAddressCmdlet : PSCmdlet
{
try
{
var controller = controllers.addresses;
var controller = ApiController.Addresses;
var identifiers = new List<string>();
switch (ParameterSetName)
@@ -92,23 +102,28 @@ public class GetAddressCmdlet : PSCmdlet
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)
{
@@ -117,42 +132,34 @@ public class GetAddressCmdlet : PSCmdlet
}
else
{
controller = controllers.subnets;
controller = ApiController.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();
controller = ApiController.Subnets;
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", SubnetCIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found.");
}
var subnetObj = subnet as Subnetwork;
identifiers.Add(subnetObj!.Id.ToString());
identifiers.Add(subnet.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, types.Address, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
var result = RequestHelper.InvokeRequest(
"GET", controller, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,20 +1,23 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Gets the first available IP address in a subnet.
/// </summary>
[Cmdlet(VerbsCommon.Get, "FirstFreeIP", DefaultParameterSetName = "ByID")]
public class GetFirstFreeIPCmdlet : PSCmdlet
public class GetFirstFreeIPCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR")]
ParameterSetName = "ByCIDR",
HelpMessage = "The subnet in CIDR notation.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? CIDR { get; set; }
@@ -24,7 +27,8 @@ public class GetFirstFreeIPCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -33,7 +37,8 @@ public class GetFirstFreeIPCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
ParameterSetName = "BySubnetObject",
HelpMessage = "The subnet object.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
@@ -41,30 +46,12 @@ public class GetFirstFreeIPCmdlet : PSCmdlet
{
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 subnetId = GetSubnetId();
var identifiers = new List<string> { subnetId.ToString(), "first_free" };
var result = RequestHelper.InvokeRequest("GET", controllers.subnets, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, null, null, null, identifiers.ToArray()
).GetAwaiter().GetResult();
if (result != null)
{
@@ -76,4 +63,28 @@ public class GetFirstFreeIPCmdlet : PSCmdlet
WriteError(new ErrorRecord(ex, "GetFirstFreeIPError", ErrorCategory.InvalidOperation, null));
}
}
private int GetSubnetId()
{
switch (ParameterSetName)
{
case "ByCIDR":
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", CIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new ItemNotFoundException($"Subnet '{CIDR}' not found.");
}
return subnet.Id;
case "BySubnetObject":
return SubnetObject!.Id;
default:
return Id!.Value;
}
}
}

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves L2 domain information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "L2Domain", DefaultParameterSetName = "ByID")]
[OutputType(typeof(Domain))]
public class GetL2DomainCmdlet : PSCmdlet
public class GetL2DomainCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The L2 domain ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -23,28 +27,18 @@ public class GetL2DomainCmdlet : PSCmdlet
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();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.L2Domains, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves nameserver information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Nameserver", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Nameserver))]
public class GetNameserverCmdlet : PSCmdlet
public class GetNameserverCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The nameserver ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -23,28 +27,18 @@ public class GetNameserverCmdlet : PSCmdlet
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();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Tools, ModelType.Nameserver, ApiSubController.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Net;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves permission information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Permissions")]
public class GetPermissionsCmdlet : PSCmdlet
public class GetPermissionsCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The address ID.")]
[ValidateNotNullOrEmpty]
public string? Id { get; set; }
@@ -23,7 +27,8 @@ public class GetPermissionsCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByIP")]
ParameterSetName = "ByIP",
HelpMessage = "The IP address to search for.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
@@ -38,7 +43,8 @@ public class GetPermissionsCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByHostName")]
ParameterSetName = "ByHostName",
HelpMessage = "The hostname to search for.")]
[ValidateNotNullOrEmpty]
public string? HostName { get; set; }
@@ -47,7 +53,8 @@ public class GetPermissionsCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByTag")]
ParameterSetName = "ByTag",
HelpMessage = "The tag ID.")]
[ValidateNotNullOrEmpty]
public string? TagId { get; set; }
@@ -56,7 +63,8 @@ public class GetPermissionsCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetId")]
ParameterSetName = "BySubnetId",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public string? SubnetId { get; set; }
@@ -65,7 +73,8 @@ public class GetPermissionsCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetCIDR")]
ParameterSetName = "BySubnetCIDR",
HelpMessage = "The subnet in CIDR notation.")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? SubnetCIDR { get; set; }
@@ -74,7 +83,7 @@ public class GetPermissionsCmdlet : PSCmdlet
{
try
{
var controller = controllers.addresses;
var controller = ApiController.Addresses;
var identifiers = new List<string>();
switch (ParameterSetName)
@@ -82,19 +91,23 @@ public class GetPermissionsCmdlet : PSCmdlet
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)
{
@@ -103,42 +116,34 @@ public class GetPermissionsCmdlet : PSCmdlet
}
else
{
controller = controllers.subnets;
controller = ApiController.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();
controller = ApiController.Subnets;
var subnet = RequestHelper.InvokeRequest(
"GET", ApiController.Subnets, ModelType.Subnetwork, null, null,
new[] { "cidr", SubnetCIDR! }
).GetAwaiter().GetResult() as Subnetwork;
if (subnet == null)
{
throw new Exception("Cannot find subnet!");
throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found.");
}
var subnetObj = subnet as Subnetwork;
identifiers.Add(subnetObj!.Id.ToString());
identifiers.Add(subnet.Id.ToString());
identifiers.Add("addresses");
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,19 +1,23 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves section information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Section", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Section))]
public class GetSectionCmdlet : PSCmdlet
public class GetSectionCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The section ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -21,7 +25,8 @@ public class GetSectionCmdlet : PSCmdlet
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByName")]
ParameterSetName = "ByName",
HelpMessage = "The section name.")]
[ValidateNotNullOrEmpty]
public string? Name { get; set; }
@@ -30,6 +35,7 @@ public class GetSectionCmdlet : PSCmdlet
try
{
var identifiers = new List<string>();
if (ParameterSetName == "ByID" && Id.HasValue)
{
identifiers.Add(Id.Value.ToString());
@@ -39,23 +45,12 @@ public class GetSectionCmdlet : PSCmdlet
identifiers.Add(Name!);
}
var result = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Sections, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,22 +1,24 @@
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;
/// <summary>
/// Retrieves subnet information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Subnet", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Subnetwork))]
public class GetSubnetCmdlet : PSCmdlet
public class GetSubnetCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByCIDR")]
ParameterSetName = "ByCIDR",
HelpMessage = "The subnet in CIDR notation (e.g., 192.168.1.0/24).")]
[ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")]
[ValidateNotNullOrEmpty]
public string? CIDR { get; set; }
@@ -26,7 +28,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The subnet ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -35,7 +38,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySectionId")]
ParameterSetName = "BySectionId",
HelpMessage = "The section ID to get subnets from.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
@@ -56,7 +60,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySectionName")]
ParameterSetName = "BySectionName",
HelpMessage = "The section name to get subnets from.")]
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
@@ -77,7 +82,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVrfId")]
ParameterSetName = "ByVrfId",
HelpMessage = "The VRF ID to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VrfId { get; set; }
@@ -86,7 +92,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanId")]
ParameterSetName = "ByVlanId",
HelpMessage = "The VLAN ID to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VlanId { get; set; }
@@ -95,7 +102,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByVlanNumber")]
ParameterSetName = "ByVlanNumber",
HelpMessage = "The VLAN number to get subnets from.")]
[ValidateNotNullOrEmpty]
public int? VlanNumber { get; set; }
@@ -104,7 +112,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "Get child subnets (slaves).")]
public SwitchParameter Slaves { get; set; }
[Parameter(
@@ -112,7 +121,8 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 2,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "Get child subnets recursively.")]
public SwitchParameter Recurse { get; set; }
[Parameter(
@@ -120,16 +130,16 @@ public class GetSubnetCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 1,
ParameterSetName = "ByVlanNumber")]
ParameterSetName = "ByVlanNumber",
HelpMessage = "The L2 domain ID to narrow VLAN search.")]
[ValidateNotNullOrEmpty]
public int? VlanDomainId { get; set; }
protected override void ProcessRecord()
{
try
{
var controller = controllers.subnets;
var controller = ApiController.Subnets;
var identifiers = new List<string>();
switch (ParameterSetName)
@@ -138,6 +148,7 @@ public class GetSubnetCmdlet : PSCmdlet
identifiers.Add("cidr");
identifiers.Add(CIDR!);
break;
case "ByID":
identifiers.Add(Id!.Value.ToString());
if (Slaves.IsPresent)
@@ -145,117 +156,122 @@ public class GetSubnetCmdlet : PSCmdlet
identifiers.Add(Recurse.IsPresent ? "slaves_recursive" : "slaves");
}
break;
case "BySectionId":
controller = controllers.sections;
controller = ApiController.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());
controller = ApiController.Sections;
var section = GetSectionByName(SectionName!);
identifiers.Add(section.Id.ToString());
identifiers.Add("subnets");
break;
case "ByVrfId":
controller = controllers.vrf;
controller = ApiController.Vrf;
identifiers.Add(VrfId!.Value.ToString());
identifiers.Add("subnets");
break;
case "ByVlanId":
controller = controllers.vlan;
controller = ApiController.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());
}
}
AddSectionIdentifier(identifiers);
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());
controller = ApiController.Vlan;
var vlan = FindVlanByNumber(VlanNumber!.Value, VlanDomainId);
identifiers.Add(vlan.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());
}
}
AddSectionIdentifier(identifiers);
break;
}
var result = RequestHelper.InvokeRequest("GET", controller, types.Subnetwork, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
var result = RequestHelper.InvokeRequest(
"GET", controller, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetSubnetError", ErrorCategory.InvalidOperation, null));
}
}
private Section GetSectionByName(string name)
{
var section = RequestHelper.InvokeRequest(
"GET", ApiController.Sections, ModelType.Section, null, null, new[] { name }
).GetAwaiter().GetResult() as Section;
if (section == null)
{
throw new ItemNotFoundException($"Section '{name}' not found.");
}
return section;
}
private Vlan FindVlanByNumber(int number, int? domainId)
{
var vlans = RequestHelper.InvokeRequest(
"GET", ApiController.Vlan, ModelType.Vlan, null, null,
new[] { "search", number.ToString() }
).GetAwaiter().GetResult();
if (vlans == null)
{
throw new ItemNotFoundException($"VLAN {number} not found.");
}
Vlan? foundVlan = null;
if (vlans is System.Collections.IEnumerable enumerable)
{
foreach (var v in enumerable)
{
if (v is Vlan vlan)
{
if (domainId.HasValue && vlan.DomainId != domainId.Value)
{
continue;
}
if (foundVlan != null)
{
throw new InvalidOperationException(
$"Multiple VLANs with number {number} exist. Specify VlanDomainId to narrow the search.");
}
foundVlan = vlan;
}
}
}
if (foundVlan == null)
{
throw new ItemNotFoundException($"VLAN {number} not found.");
}
return foundVlan;
}
private void AddSectionIdentifier(List<string> identifiers)
{
if (SectionId.HasValue)
{
identifiers.Add(SectionId.Value.ToString());
}
else if (!string.IsNullOrEmpty(SectionName))
{
var section = GetSectionByName(SectionName);
identifiers.Add(section.Id.ToString());
}
}
}

View File

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

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves tag information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Tag", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Tag))]
public class GetTagCmdlet : PSCmdlet
public class GetTagCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The tag ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -23,7 +27,8 @@ public class GetTagCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject")]
ParameterSetName = "ByAddressObject",
HelpMessage = "Get the tag associated with this address.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
@@ -32,7 +37,8 @@ public class GetTagCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
ParameterSetName = "BySubnetObject",
HelpMessage = "Get the tag associated with this subnet.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
@@ -50,6 +56,7 @@ public class GetTagCmdlet : PSCmdlet
identifiers.Add(Id.Value.ToString());
}
break;
case "ByAddressObject":
if (AddressObject?.TagId > 0)
{
@@ -60,6 +67,7 @@ public class GetTagCmdlet : PSCmdlet
return;
}
break;
case "BySubnetObject":
if (SubnetObject?.TagId > 0)
{
@@ -72,23 +80,11 @@ public class GetTagCmdlet : PSCmdlet
break;
}
var result = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Tag, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Addresses, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves VLAN information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Vlan", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vlan))]
public class GetVlanCmdlet : PSCmdlet
public class GetVlanCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The VLAN ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -23,7 +27,8 @@ public class GetVlanCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByNumber")]
ParameterSetName = "ByNumber",
HelpMessage = "The VLAN number.")]
[ValidateNotNullOrEmpty]
public int? Number { get; set; }
@@ -32,7 +37,8 @@ public class GetVlanCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByL2Domain")]
ParameterSetName = "ByL2Domain",
HelpMessage = "The L2 domain ID to get VLANs from.")]
[ValidateNotNullOrEmpty]
public int? L2DomainId { get; set; }
@@ -41,7 +47,8 @@ public class GetVlanCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "BySubnetObject")]
ParameterSetName = "BySubnetObject",
HelpMessage = "Get the VLAN associated with this subnet.")]
[ValidateNotNullOrEmpty]
public Subnetwork? SubnetObject { get; set; }
@@ -50,7 +57,8 @@ public class GetVlanCmdlet : PSCmdlet
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByDomainObject")]
ParameterSetName = "ByDomainObject",
HelpMessage = "Get VLANs in this L2 domain.")]
[ValidateNotNullOrEmpty]
public Domain? DomainObject { get; set; }
@@ -58,7 +66,7 @@ public class GetVlanCmdlet : PSCmdlet
{
try
{
var controller = controllers.vlan;
var controller = ApiController.Vlan;
var identifiers = new List<string>();
switch (ParameterSetName)
@@ -66,65 +74,44 @@ public class GetVlanCmdlet : PSCmdlet
case "ByID":
identifiers.Add(Id!.Value.ToString());
break;
case "ByNumber":
identifiers.Add("search");
identifiers.Add(Number!.Value.ToString());
break;
case "ByL2Domain":
controller = controllers.l2domains;
controller = ApiController.L2Domains;
identifiers.Add(L2DomainId!.Value.ToString());
identifiers.Add(subcontrollers.vlans.ToString());
identifiers.Add(ApiSubController.Vlans.ToString().ToLowerInvariant());
break;
case "BySubnetObject":
if (SubnetObject != null && SubnetObject.VlanId > 0)
{
identifiers.Add(SubnetObject.VlanId.ToString());
}
else
if (SubnetObject == null || SubnetObject.VlanId <= 0)
{
return;
}
identifiers.Add(SubnetObject.VlanId.ToString());
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);
}
}
var result = RequestHelper.InvokeRequest(
"GET", ApiController.L2Domains, ModelType.Vlan, ApiSubController.Vlans,
null, new[] { DomainObject.Id.ToString() }
).GetAwaiter().GetResult();
WriteResult(result);
}
return;
}
var vlanResult = RequestHelper.InvokeRequest("GET", controller, types.Vlan, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null)
.GetAwaiter().GetResult();
var vlanResult = RequestHelper.InvokeRequest(
"GET", controller, ModelType.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);
}
}
WriteResult(vlanResult);
}
catch (Exception ex)
{

View File

@@ -1,20 +1,24 @@
namespace PS.IPAM.Cmdlets;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PS.IPAM;
using PS.IPAM.Helpers;
/// <summary>
/// Retrieves VRF information from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Get, "Vrf", DefaultParameterSetName = "NoParams")]
[OutputType(typeof(Vrf))]
public class GetVrfCmdlet : PSCmdlet
public class GetVrfCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByID")]
ParameterSetName = "ByID",
HelpMessage = "The VRF ID.")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
@@ -23,28 +27,18 @@ public class GetVrfCmdlet : PSCmdlet
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();
var result = RequestHelper.InvokeRequest(
"GET", ApiController.Vrf, ModelType.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);
}
}
WriteResult(result);
}
catch (Exception ex)
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +1,31 @@
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
/// <summary>
/// Removes an IP address entry from phpIPAM.
/// </summary>
[Cmdlet(VerbsCommon.Remove, "Address", DefaultParameterSetName = "ById", SupportsShouldProcess = true)]
public class RemoveAddressCmdlet : BaseCmdlet
{
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByID")]
[ValidateNotNullOrEmpty]
public int? Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
Position = 0,
ParameterSetName = "ByAddressObject")]
ParameterSetName = "ById",
HelpMessage = "The address ID to remove.")]
[ValidateNotNullOrEmpty]
public int Id { get; set; }
[Parameter(
Mandatory = true,
ValueFromPipeline = true,
Position = 0,
ParameterSetName = "ByAddressObject",
HelpMessage = "The address object to remove.")]
[ValidateNotNullOrEmpty]
public Address? AddressObject { get; set; }
@@ -29,19 +33,17 @@ public class RemoveAddressCmdlet : PSCmdlet
{
try
{
int addressId;
if (ParameterSetName == "ByID")
{
addressId = Id!.Value;
}
else
{
addressId = AddressObject!.Id;
}
var addressId = ParameterSetName == "ById" ? Id : AddressObject!.Id;
var identifiers = new[] { addressId.ToString() };
var identifiers = new List<string> { addressId.ToString() };
RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, identifiers.ToArray())
.GetAwaiter().GetResult();
if (ShouldProcess($"Address ID: {addressId}", "Remove"))
{
RequestHelper.InvokeRequest(
"DELETE", ApiController.Addresses, null, null, null, identifiers
).GetAwaiter().GetResult();
WriteVerbose($"Address {addressId} removed successfully.");
}
}
catch (Exception ex)
{

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,60 @@
namespace PS.IPAM.Helpers;
using System;
using System.Management.Automation;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PS.IPAM;
/// <summary>
/// Manages phpIPAM API sessions including creation, validation, and lifecycle.
/// </summary>
public static class SessionManager
{
private static Session? _currentSession;
/// <summary>
/// Gets or sets the current active session.
/// </summary>
public static Session? CurrentSession
{
get => _currentSession;
set => _currentSession = value;
}
public static string TestSession()
/// <summary>
/// Tests the current session status.
/// </summary>
/// <returns>The session status indicating validity or issues.</returns>
public static SessionStatus GetSessionStatus()
{
if (_currentSession == null)
{
return "NoToken";
return SessionStatus.NoSession;
}
if (_currentSession.Expires == null)
{
return "Valid";
return SessionStatus.Valid;
}
if (_currentSession.Expires < DateTime.Now)
{
return "Expired";
}
return "Valid";
return _currentSession.Expires < DateTime.Now
? SessionStatus.Expired
: SessionStatus.Valid;
}
public static async Task<Session> CreateSessionWithCredentials(
/// <summary>
/// Creates a new session using username/password credentials.
/// </summary>
/// <param name="url">The phpIPAM server URL.</param>
/// <param name="appId">The API application ID.</param>
/// <param name="credentials">The PowerShell credential object.</param>
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
/// <returns>The created session.</returns>
public static async Task<Session> CreateSessionWithCredentialsAsync(
string url,
string appId,
PSCredential credentials,
@@ -46,7 +62,7 @@ public static class SessionManager
{
var uri = $"{url}/api/{appId}/user";
var auth = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPassword(credentials)}"));
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPasswordString(credentials)}"));
using var client = CreateHttpClient(ignoreSsl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -58,14 +74,15 @@ public static class SessionManager
if (jsonResponse?.success != true)
{
throw new Exception(jsonResponse?.error?.ToString() ?? "Failed to create session");
throw new InvalidOperationException(
jsonResponse?.error?.ToString() ?? "Failed to create session");
}
var token = jsonResponse.data.token.ToString();
var expires = DateTime.Parse(jsonResponse.data.expires.ToString());
_currentSession = new Session(
AuthType.credentials,
AuthType.Credentials,
token,
appId,
url,
@@ -76,13 +93,17 @@ public static class SessionManager
return _currentSession;
}
public static Session CreateSessionWithToken(
string url,
string appId,
string token)
/// <summary>
/// Creates a new session using a static API token.
/// </summary>
/// <param name="url">The phpIPAM server URL.</param>
/// <param name="appId">The API application ID.</param>
/// <param name="token">The API token.</param>
/// <returns>The created session.</returns>
public static Session CreateSessionWithToken(string url, string appId, string token)
{
_currentSession = new Session(
AuthType.token,
AuthType.Token,
token,
appId,
url,
@@ -93,24 +114,20 @@ public static class SessionManager
return _currentSession;
}
/// <summary>
/// Closes the current session and clears session data.
/// </summary>
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);
}
}
/// <summary>
/// Creates an HttpClient with optional SSL bypass.
/// </summary>
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
/// <param name="handler">Optional custom message handler for testing.</param>
/// <returns>A configured HttpClient instance.</returns>
public static HttpClient CreateHttpClient(bool ignoreSsl = false, HttpMessageHandler? handler = null)
{
if (handler != null)
@@ -122,10 +139,27 @@ public static class SessionManager
{
var sslHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
return new HttpClient(sslHandler);
}
return new HttpClient();
}
/// <summary>
/// Extracts the plain text password from a PSCredential object.
/// </summary>
private static string GetPasswordString(PSCredential credential)
{
var ptr = Marshal.SecureStringToBSTR(credential.Password);
try
{
return Marshal.PtrToStringBSTR(ptr);
}
finally
{
Marshal.ZeroFreeBSTR(ptr);
}
}
}

View File

@@ -1,77 +1,35 @@
namespace PS.IPAM;
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents an IP address entry in phpIPAM.
/// </summary>
[Serializable]
public class Address {
public int Id { get; }
public int SubnetId { get; }
public string Ip { get; }
public bool IsGateway { get; }
public string Description { get; }
public string Hostname { get; }
public string MAC { get; }
public string Owner { get; }
public int TagId { get; }
public int DeviceId { get; }
public string Location { get; }
public string Port { get; }
public string Note { get; }
public DateTime? LastSeen { get; }
public bool ExcludePing { get; }
public bool PTRignore { get; }
public int PTR { get; }
public string FirewallAddressObject { get; }
public DateTime? EditDate { get; }
public int CustomerId { get; }
public Object? ExtendedData { get; }
public Address(
int id,
int subnetId,
string ip,
bool is_gateway,
string description,
string hostname,
string mac,
string owner,
int tag,
int deviceId,
string location,
string port,
string note,
DateTime? lastSeen,
bool excludePing,
bool PTRignore,
int PTR,
string firewallAddressObject,
DateTime? editDate,
int customer_id,
Object? extendedData
) {
this.Id = id;
this.SubnetId = subnetId;
this.Ip = ip;
this.IsGateway = is_gateway;
this.Description = description;
this.Hostname = hostname;
this.MAC = mac;
this.Owner = owner;
this.TagId = tag;
this.DeviceId = deviceId;
this.Location = location;
this.Port = port;
this.Note = note;
this.LastSeen = lastSeen;
this.ExcludePing = excludePing;
this.PTRignore = PTRignore;
this.PTR = PTR;
this.FirewallAddressObject = firewallAddressObject;
this.EditDate = editDate;
this.CustomerId = customer_id;
this.ExtendedData = extendedData;
}
public override string ToString() {
return this.Ip;
}
}
public sealed record Address(
int Id,
int SubnetId,
string Ip,
bool IsGateway,
string Description,
string Hostname,
string MAC,
string Owner,
int TagId,
int DeviceId,
string Location,
string Port,
string Note,
DateTime? LastSeen,
bool ExcludePing,
bool PTRIgnore,
int PTR,
string FirewallAddressObject,
DateTime? EditDate,
int CustomerId,
Dictionary<string, object>? ExtendedData = null
)
{
public override string ToString() => Ip;
}

View File

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

View File

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

View File

@@ -1,57 +1,27 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents a section in phpIPAM that organizes subnets.
/// </summary>
[Serializable]
public class Section {
public int Id { get; }
public string Name { get; }
public string Description { get; }
public int MasterSectionId { get; }
public string Permissions { get; }
public bool StrictMode { get; }
public string SubnetOrdering { get; }
public int Order { get; }
public DateTime? EditDate { get; }
public bool ShowSubnet { get; }
public bool ShowVlan { get; }
public bool ShowVRF { get; }
public bool ShowSupernetOnly { get; }
public int DNSId { get; }
public Section (
int id,
string name,
string description,
int masterSectionId,
string permissions,
bool strictMode,
string subnetOrdering,
int order,
DateTime? editDate,
bool showSubnet,
bool showVlan,
bool showVRF,
bool showSupernetOnly,
int dnsId
) {
this.Id = id;
this.Name = name;
this.Description = description;
this.MasterSectionId = masterSectionId;
this.Permissions = permissions;
this.StrictMode = strictMode;
this.SubnetOrdering = subnetOrdering;
this.Order = order;
this.EditDate = editDate;
this.ShowSubnet = showSubnet;
this.ShowVlan = showVlan;
this.ShowVRF = showVRF;
this.ShowSupernetOnly = showSupernetOnly;
this.DNSId = dnsId;
}
public override string ToString()
{
return this.Name;
}
}
public sealed record Section(
int Id,
string Name,
string Description,
int MasterSectionId,
string Permissions,
bool StrictMode,
string SubnetOrdering,
int Order,
DateTime? EditDate,
bool ShowSubnet,
bool ShowVlan,
bool ShowVRF,
bool ShowSupernetOnly,
int DNSId
)
{
public override string ToString() => Name;
}

View File

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

View File

@@ -1,114 +1,51 @@
namespace PS.IPAM;
using System;
using System.Collections.Generic;
/// <summary>
/// Represents a subnet/network in phpIPAM.
/// </summary>
[Serializable]
public class Subnetwork {
public int Id { get; }
public string Subnet { get; }
public int Mask { get; }
public int SectionId { get; }
public string Description { get; }
public string LinkedSubnet { get; }
public string FirewallAddressObject { get; }
public int VrfId { get; }
public int MasterSubnetId { get; }
public bool AllowRequests { get; }
public int VlanId { get; }
public bool ShowName { get; }
public int DeviceId { get; }
public string Permissions { get; }
public bool PingSubnet { get; }
public bool DiscoverSubnet { get; }
public bool ResolveDNS { get; }
public bool DNSRecursive { get; }
public bool DNSRecords { get; }
public int NameserverId { get; }
public bool ScanAgent { get; }
public bool IsFolder { get; }
public bool IsFull { get; }
public bool IsPool { get; }
public int TagId { get; }
public int Threshold { get; }
public int LocationId { get; }
public DateTime? EditDate { get; }
public DateTime? LastScan { get; }
public DateTime? LastDiscovery { get; }
public Object Calculation { get; }
public Object? ExtendedData { get; }
public Subnetwork(
int id,
string subnet,
int mask,
int sectionId,
string description,
string linkedSubnet,
string firewallAddressObject,
int vrfId,
int masterSubnetId,
bool allowRequests,
int vlanId,
bool showName,
int deviceId,
string permissions,
bool pingSubnet,
bool discoverSubnet,
bool resolveDNS,
bool dnsRecursive,
bool dnsRecords,
int nameserverId,
bool scanAgent,
bool isFolder,
bool isFull,
bool isPool,
int tagId,
int threshold,
int locationId,
DateTime? editDate,
DateTime? lastScan,
DateTime? lastDiscovery,
Object calculation,
Object? custom_fields
) {
this.Id = id;
this.Subnet = subnet;
this.Mask = mask;
this.SectionId = sectionId;
this.Description = description;
this.LinkedSubnet = linkedSubnet;
this.FirewallAddressObject = firewallAddressObject;
this.VrfId = vrfId;
this.MasterSubnetId = masterSubnetId;
this.AllowRequests = allowRequests;
this.VlanId = vlanId;
this.ShowName = showName;
this.DeviceId = deviceId;
this.Permissions = permissions;
this.PingSubnet = pingSubnet;
this.DiscoverSubnet = discoverSubnet;
this.ResolveDNS = resolveDNS;
this.DNSRecursive = dnsRecursive;
this.DNSRecords = dnsRecords;
this.NameserverId = nameserverId;
this.ScanAgent = scanAgent;
this.IsFolder = isFolder;
this.IsFull = isFull;
this.IsPool = isPool;
this.TagId = tagId;
this.Threshold = threshold;
this.LocationId = locationId;
this.EditDate = editDate;
this.LastScan = lastScan;
this.LastDiscovery = lastDiscovery;
this.Calculation = calculation;
this.ExtendedData = custom_fields;
}
public sealed record Subnetwork(
int Id,
string Subnet,
int Mask,
int SectionId,
string Description,
string LinkedSubnet,
string FirewallAddressObject,
int VrfId,
int MasterSubnetId,
bool AllowRequests,
int VlanId,
bool ShowName,
int DeviceId,
string Permissions,
bool PingSubnet,
bool DiscoverSubnet,
bool ResolveDNS,
bool DNSRecursive,
bool DNSRecords,
int NameserverId,
bool ScanAgent,
bool IsFolder,
bool IsFull,
bool IsPool,
int TagId,
int Threshold,
int LocationId,
DateTime? EditDate,
DateTime? LastScan,
DateTime? LastDiscovery,
object Calculation,
Dictionary<string, object>? ExtendedData = null
)
{
/// <summary>
/// Gets the subnet in CIDR notation (e.g., "192.168.1.0/24").
/// </summary>
public string CIDR => $"{Subnet}/{Mask}";
public string GetCIDR() {
return $"{this.Subnet}/{this.Mask}";
}
public override string ToString()
{
return this.GetCIDR();
}
}
public override string ToString() => CIDR;
}

View File

@@ -1,45 +1,21 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents an address tag in phpIPAM.
/// </summary>
[Serializable]
public class Tag {
public int Id { get; }
public string Type { get; }
public bool ShowTag { get; }
public string BGColor { get; }
public string FGColor { get; }
public bool Compress { get; }
public bool Locked { get; }
public bool UpdateTag { get; }
public Tag(
int id,
string type,
bool showTag,
string BGColor,
string FGColor,
string compress,
string locked,
bool updateTag
) {
this.Id = id;
this.Type = type;
this.ShowTag = showTag;
this.BGColor = BGColor;
this.FGColor = FGColor;
this.Compress = this.StringToBool(compress);
this.Locked = this.StringToBool(locked);
this.UpdateTag = updateTag;
}
public override string ToString() {
return this.Type;
}
private bool StringToBool(string str) {
if (str == "Yes") {
return true;
}
return false;
}
}
public sealed record Tag(
int Id,
string Type,
bool ShowTag,
string BackgroundColor,
string ForegroundColor,
string Compress,
string Locked,
bool UpdateTag
)
{
public override string ToString() => Type;
}

View File

@@ -1,41 +1,27 @@
namespace PS.IPAM;
using System;
using System.Dynamic;
using System.Collections.Generic;
/// <summary>
/// Represents a VLAN in phpIPAM.
/// </summary>
[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; }
public string Description { get; }
public DateTime? EditDate { get; }
public int CustomerId { get; }
public Object? ExtendedData { get; }
public Vlan (
int vlanId,
int domainId,
string name,
int number,
string description,
DateTime? editDate,
int customer_id,
Object? custom_fields
) {
this.Id = vlanId;
this.VlanId = vlanId;
this.DomainId = domainId;
this.Name = name;
this.Number = number;
this.Description = description;
this.EditDate = editDate;
this.CustomerId = customer_id;
this.ExtendedData = custom_fields;
}
public sealed record Vlan(
int Id,
int DomainId,
string Name,
int Number,
string Description,
DateTime? EditDate,
int CustomerId,
Dictionary<string, object>? ExtendedData = null
)
{
/// <summary>
/// Alias for Id to maintain API compatibility.
/// </summary>
public int VlanId => Id;
public override string ToString()
{
return $"{this.Number}";
}
}
public override string ToString() => Number.ToString();
}

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,21 @@
namespace PS.IPAM;
using System;
/// <summary>
/// Represents sub-controllers/endpoints within main API controllers.
/// </summary>
[Serializable]
public enum subcontrollers {
nameservers,
tags,
devices,
device_types,
vlans,
vrfs,
scanagents,
locations,
nat,
racks
}
public enum ApiSubController
{
Nameservers,
Tags,
Devices,
DeviceTypes,
Vlans,
Vrfs,
ScanAgents,
Locations,
Nat,
Racks
}

View File

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