Building cmdlets for a REST API with PowerShell classes, Azure-based integration tests and continuous deployment
Murrah, Jeremy
This talk will cover a project to write a set of cmdlets for an Infoblox dns appliance. Code is split between cmdlets for the interface and parameter validation, and classes for modeling all the REST calls. Will also cover the CD pipeline via appveyor with tests against an Azure appliance. Classes, the new feature in PowerShell 5.0, was added just for writing DSC resources right? Not so! Come see how classes can be used as part of a PowerShell cmdlet, providing code separation, easier parameter validation, and more. This session will cover a working module that uses class definitions alongside standard PowerShell cmdlets to integrate with a REST API. Learn how using defined classes makes integration between get and set cmdlets easier and potentially safer. It will also cover the release pipeline for testing and deployment, with a little Azure Resource Manager template design thrown in for fun. All code for this session will be available on GitHub. This session will cover: - How to build a REST Uri and payload from user-provided parameter values - How to model objects and build class definitions and methods to reflect actions on those objects - How to build cmdlets that interface with both the user and the class definitions, isolating code for improved readability - How to build a test environment in Azure for running Pester tests and deploy to the PS Gallery with Appveyor This has been an amazing side project that I've been working on for over a year. I've learned a ton about classes, github, appveyor, azure, etc. and I'm eager to share all that.
 Morning everybody, my name is Jeremy Murrah and I'm here to talk about, I need a deep breath to say all that, building commandlets for REST API with PowerShell classes, Azure based integration tests and continuous deployment. They gave me a double link session so I'd have time to read all of that. And the reason there's so much up there is rather than do a deep dive on a single topic like Kirk's debugging session or some of the other ones, I wanted to kind of cover an end to end scenario, like a project you might do, where say you start with this super important REST API that you need to wrangle, that's not swagger based because you can just let that write its own code, but say you've got this API, they didn't write their own PowerShell commandlets for whatever reason, they think it's a fad and we're all going to switch back to Perl or something. But you've got this API and you're going to go through the process of turning that into something that's a lot more user friendly, but you're going to keep going. And ideally, ultimately end up with something that you publish out on the PowerShell gallery so all the rest of us can use it and then I'll have to go through this process. So I kind of wanted to touch on all the pieces of that that you might see. So we're going to go through all of that. This was the specifics for my demo code and my project was something I did with, it's a device called Infoblox, that's the company name, I forget the appliance. If anybody is familiar, it's a DNS, DHCP, IPAM kind of appliance. So yeah, we have that, they have an API, they're sticking to their Perl API, they're sticking to their guns. So they have a REST API and all that. So I kind of went through a process of writing commandlets for that and just kind of kept going and it's out on the gallery and that was super useful. So some of the stuff is specific to that but I'm going to try to separate out the specifics of that implementation and point out what's generic to this kind of structure as a concept that you can reuse for something else. Anyway, so that's the idea. So let's do that. So what a REST API is, well it's a lot of things but at its real basic core, it's a URL representation of an object or multiple objects and then you've got your standard methods that you can perform against it, right? Your HTTP calls, your git, your post, your delete, you know, pretty basic stuff there and there's some extra, some functions and extra, but that's the general idea. Classes are a similar kind of thing. You've got a representation of an object with defined properties and then these static methods. And looking at this, there was, seemed to be, you know, a fair bit of carryover, kind of a one-to-one mapping. So that's kind of the genesis of where this idea came from. That and also PowerShell 5 had just come out and I was looking for an excuse to play with classes because why not, right? So this kind of started in there. But why classes? You can do this with helper functions or you can just do this, you've always been able to do this with standard PowerShell. We're just doing invoke REST method after all. Well, so like I mentioned, part of why I started was really just to kind of play with it, right? And try something new that hadn't been done before as far as I knew. What I found was a couple of really interesting sort of benefits to it when I started playing with it. What, you know, we talk about in PowerShell, the thing is always have a function that does one thing, right? And that's for readability and that's for ease of use, ease of coding later when you're trying to figure out what you did. And so that's a common model and that's really helpful and when I started looking at doing with the REST API and putting a commandlet in a way, even unlike a get, you know, get some data of this particular type, you're kind of doing two things. You can think about it. You've got your invoke REST method and you've got to build that URI string or that payload and do all this work to parse that data and then send it off and then get your return. Hopefully instead of a 400 you get an actual return and then you've got to do something to maybe parse that output or do some logic around it. So that's all the API interaction code. Then you've got your parameters and your input that you're taking from the users and any output you send them and maybe some extra validation, all that user friendly, gooey, touchy-feely stuff. So in a way those are kind of two separate things. And you can do it all together. It's not terribly complicated but what I found is if you set your class definitions as kind of that API interface. So you're going to make an object and that object is going to be that direct representation of your API object. And all of the work that you do when you are connecting to that API and that representation, that's all going to happen in your class structure. It's a whole separate file or a whole separate bit of code. And that frees you up in your commandlet to focus on all your touchy-feely user stuff, your parameter validation, maybe some extra logic, error handling. APIs love to give you that 400. Any little thing wrong and you get a 400. There's an error buried in there somewhere. Now that I'm an expert on debugging from Kirk's class, I might figure all that out. But it's difficult. So maybe you do some extra logic ahead of time. And because you've separated out some of the API work, and we'll see this in some of the code, it makes it really simple to sort of expand upon that because you've got that separation. So I thought that was pretty interesting. I'm going to start going through this. Another thing that's really cool is kind of what you get for free with PowerShell. You know, PowerShell loves objects. We all know that. And when you do objects in and objects out, you get a lot of stuff for free. Right? You get all that pipelining and you get stuff like FormatTable and FormatList. That all comes for free. Well, PowerShell really likes typed objects, objects of certain type. When you can tell it just something other than a PS object, you get even more for free. So on the left is something you might do. Let's say you have a, you've got a commandlet that you're going to do a set. So you've already written the commandlet that retrieves some data from your API or from some other source. You want to, because this is PowerShell, you want to pipe that directly over to your set command. And what you want to do without any, you know, too much fuss, but if you're passing in a PS object, you want to have a, make some assumptions about that input, right? You need to know that, okay, I'm expecting that this PS object is coming from my get command, but I can't guarantee it, right? A PS object can come from anywhere. You can craft it by hand or it can come from import CSV. You don't really know. So maybe I'm going to use the value from pipeline by property name and I'll say, okay, I expect that my input object should have a name property and maybe I've got a numerical value and a string value. So I'm going to define all these because I'm going to, I'm going to need that data anyway, but also I want to make sure that my object is the right kind of object. All right, so maybe it passes all these checks and I didn't put all this, this would have been a horrible slide, but maybe it passes those checks. But you still want to understand that this is a valid representation of a live object on your API endpoint, right? It may be that kind of PS object, but that could have been something somebody dumped out to a spreadsheet a month ago. And now they're telling you, hey, go clean this up. So you can't assume that it's valid. So you'd probably have to go after all of this and maybe a begin block or, well, I guess it'd be your process block is go out to your endpoint and do a retrieval anyway and validate that, that object that you're going to act upon is really out there and it really has all this data. Especially before you're doing something like a remove. You really want to make sure you're removing the right thing. So you've got to do a lot of work in your command let just to validate your data. Well here on the right, if we have our API object defined from a class structure, we've got a very specific object type. And so we can specify that on our single input object. We take the entire object in, it's of this certain type. It will only pass our parameter validation if it is of that type. And what that tells us or gives us an assumption, we wrote that class definition so we know all the properties. It has to be of that type. We know that or we can have a good enough assumption that it is a valid object on our API endpoint because it's of fewer places that that class can come from. It's going to come from our class definition that did that call and retrieved that object. So this is not a security boundary by any means. Somebody can just make a class themselves and if it was an intent to be malicious, that is still going to happen but that's not the point of parameter validation. This is the save you from causing horrible harm when you're passing in the wrong kind of thing. So you get a lot of that kind of stuff for free because you've defined the object of that type. So you can turn a whole lot of code into one line in a way and that, again, frees you up to do more of the user-friendly GUI stuff. And then, of course, because this is PowerShell and we all like having fun, why not? You never know if something is a fit for the job until you try it. That's my five-year-old doing some experimentation of his own. They said we can't use copyrighted images but I figure I own the copyright on that, both the picture and the subject. So experimentation, that's why we're here, right? So that's enough slides for a little while. Oh, that next is a good one, yeah. So let's switch over and... Oh, wait, I was kidding. There we go. Let's look at some of this here. Let's see. Where am I at? I should have loaded all this up ahead of time. There we go. So I'm going to walk through this here. Let me hide this. Is size okay? You guys see that in the back? Okay. It's horrible up here, these tiny screens killing me. Okay. So this is your basic, what a class might look like. Like I mentioned, I'm doing my use case here as a DNS appliance. So I picked the DNSA record. Everyone's favorite granddad, the DNSA record, to represent with this class. And so you have your... The kind of properties you'd expect on a record, right? A name and an IP address. We get some extra stuff and some of the... Again, this is all the API specific stuff, your properties, and that's going to be defined by your API and hopefully your documentation is really good because that's what makes or breaks an API. So they have defined this object as having these properties and I'm using those here. So we've got a comment is something the InfoLux gives us this ability to put a comment. I've got this... Under score ref is really interesting, that's a unique identifier for every kind of object that the API endpoint gives us. So every A record, every CNAME, every address reservation has a different one of those. And I found out you can actually start a variable with an underscore. So that's pretty interesting. It makes for some funny looking code, especially when it's a property and you've got something dot underscore, but it totally works. That was kind of interesting. So anyway, you've got all your properties that would define whatever the object is going to be. And then you've got your constructors here, which is really just like a new method, right? This is how you, when you are instantiating an object from this class, these are the properties that you need to build the object. And you can do some really complex things here or not. So we've got to just pass in the properties that are defined at the top. Say you must fill in all of these in order to create an object of this type. That just gets interesting, is in the methods, right? Let me just take a sit here. So we talked about, you know, an API has your crud, your create, your read, your update, delete. Those are primarily your four methods of interacting with an API. So we're just going to represent or create a method, a one-to-one method for each of those. All right? So I'm going to walk through these a little bit. I'm trying not to be too tedious with all the code. But so a method, I like to think of a method for me anyway as kind of a little mini function within my class, right? So I've got all these properties here. And these are not, they're similar to the properties that we define in the class, but they're not really related. These are the properties or the inputs that I need in order to execute this method. And that's it. The fact that they are similar to the class makes sense because I'm creating a DNA record. But I could have a method that took completely different inputs depending upon what I want to do. It's a little bit of separation there, but thinking about it like a little function inside your class is kind of how I like to think about it. So a couple of things here. We look at line 14, right? This is the beginning of that. So this is a static method, meaning I don't already have to have an object defined in order to run this method, which makes sense for a create. I'm not going to create an object upon an object that I've already created. I'm going to create an object out of nothing. So we've got a designation here, IBDNSA record. That's the name of my class. So what I'm telling it here is the output from this method is going to be output an object of this type. And that's super important for the parameter validation stuff when we get to the command list, what I'm showing there. It's important. The object output type is really important. You could have, you know, system.object and you wouldn't lose any of the benefits, but that would work. And then the name is whatever you want to call it. It doesn't have to match up with any sort of API verb or anything like that. It's just whatever you want to call it. So the general flow, the body of these, of all the methods are pretty similar. The idea here is you're taking these inputs and doing whatever processing you need to build your payload. And that payload is your URI string, which is that, you know, the URL, the reference to where that object is on your API endpoint. And then optionally, some sort of payload, some sort of body, right, JSON or hash table. You know, that's optional because in case of, and we'll see with like a retrieval or a get or query, you can do all of that on the string. You don't need a payload at all. For something like a put or a post, in this case, it's good to have. So that's, and that's kind of the pattern for all of them is, that's, and this is all the work you would do if you were doing just doing this in PowerShell without classes is you would just go through the logic of building that data set and then you just call invoke request method to submit it, hopefully it passes and you get a return object and then doing some work upon that return object, depending on what that return object is. So, in this case, for, you know, different, on my API endpoint, different object or different methods return different results. So in the case of a create, it doesn't actually return to me back my entire record. It returns back that unique reference identifier that I mentioned. This is this big long string. It's like a GUID without dashes, basically. So it returns that. Now what I need to do, and Snowvers talked about this a couple times, where the return, the way you output things from classes is very different from, from Standard PowerShell, right? Standard PowerShell, you just kind of have to throw it out there to the universe and it'll eventually plinko down and it'll end up out on your screen or in whatever your output is. You don't do those classes. You have to define one and only one and not less than one output objects. And everything has to return an object, a single return. So you've got to define that and that's, that, that takes some getting used to. But it's nice because you don't accidentally end up with extra output. You know, if you're ever trying to use return and then you have some, something that you didn't catch and it spits it out to the screen, you end up with some garbage output. So you can't do that with classes, but you do have to be aware. So what I'm doing, what I've chosen to do here, and this is just a design choice, what you ultimately need to do because of the output we define is you need to craft an object of this type, IBD NSA record, through some method, craft that object and then that's your return. So what I could have done here is done the colon, colon, new and called the constructor because I've got all the information I need, right? I've got all these properties that came in to my method. I could just build that up. What I've chosen to do instead is call my next method, the get method, just kind of an extra check. So I'm going to send that information that it gave me. In that case, it's that unique reference identifier. I'm going to submit that back to my end point and say, hey, I know you just created this, but can you give it to me again? And when I get that back, the way it's defined, the get method on my API endpoint is defined is to return the entire object. So it's kind of an extra validation for all my properties. If it decided to change the name just slightly or on its end or change my, say my IP address or something, I get all that by just doing that query again. So it's an extra check and it doesn't cost me anything. And then this is also that important part. If I don't get any return back, if it fails or whatever, I have to return something. In this case, I'm just going to return $. But you have to have a return statement for every end point. There's this nice useful error that'll say something, IntelliSense will pop it up and it'll tell you something like all code paths, don't return an output or something like that. So that's super important. So that's kind of a create method. And I'm going to walk through get next because I want to point out how I've used that. And with get, you have actually a couple of different options. This expansion here. So we actually have two get methods we're going to go through because there's two different ways you might do stuff. So this is the get method that I just, I call it from create, right? I've got my unique reference identifier and I'm just going to pass that back to my endpoint. And I've got some extra properties here and I'm going to talk about authentication very briefly because that's very API specific and how you want to handle that. But I've got this, I've got my session which is the web session that I catch from invoke rest method. And that's a nice and easy way of doing authentication. If your API lets you get away with that, then you're in good shape because that's a nice, there are harder ways to do authentication. So I'm passing my session in and I've chosen to, and this is a design choice. It's arguable where you want to put that. But what I've chosen to do is rather than do any authentication in my methods, in my classes, is handle the authentication and creating that session outside of that and then just pass it in. So, you know, six to one half a dozen. It saves me a little typing doing it once ahead of time. And when I get into the command, I'll kind of show you the choices I made there. But anyway, so I'm going to pass in that session is required obviously to have a connection and then there's some information I use to build my URI string that reference identifier. You can see here I don't have a hash table or a body of any type. This is an option with certain methods you can just query. And again, this is up to your API. But I can just do a single URI string to get my return. And in the same way as the create method, I call invoke rest method, get my return object back. But in this case, I'm using that constructor, that new method to craft my object. And I have to do some formatting on the IP address. But using all my return, my return object properties, I just build that object and then I return it or return no. Same kind of rules apply. I've got to return something. So and that's great for when you have that unique reference identifier. But most of the time you don't, right? You're, especially if your API didn't have that. If you are trying to get say every DNS A record for this one server or everyone that has this IP address because maybe I've got some duplicates, right? You're going to need to do a query, which is going to be your common way of interacting with an API. So this would not work at all for that, right? I'm not retrieving, I'm not going to have any of the inputs that I need coming into my method at all to even do a query. So I'm going to make an entirely new method. What they call an overload is what the developers like to call it. So which ultimately is just another method with the same name. And so these are, I'm doing the same thing. I'm doing a get, but I'm doing it in a completely different way with a completely different set of inputs. So rather than try to just have one big one and do if this and that, I'll just make a whole separate one. So it flows more logically because it's a different kind of operation. It starts out similarly. You see this is also a static method because again we don't already have an object, we're getting a new one. Now there's a little thing here with the output. Notice it's the IBD NSA record, but it's that collection notation, that extra set of braces. And that's important because in this case we are returning potentially more than one object, right? Because you do a query, with that unique reference saying you're only getting that one record with this, you're potentially getting more than one. And because of the way classes handle outputs and because of the way we want to be able to do parameter validation when we're wrapping commandlets around this, that's super important to have. It's got some weird little quirks. I'll show you that in the demo, it gets kind of weird, but that's important to have. And again it's a git method. So it's the same kind of thing. And you'll see our properties, we've got a whole lot more coming in, right? We've got our name and our p address and comment and zone and all these are all things that we could potentially be querying upon. And that's defined again by our API documentation. They have said that we can search by all these things. If they didn't allow us to search by something, we'd just leave it out. So there's a little more work going on here, but it's the same kind of thing. We're taking all those inputs and just building up, in this case just building up a very long URL, adding each property. Now the interesting thing with classes and why we have this laid out the way it is is none of these input properties are optional. You have to put something in there. You know, with some of the.NET stuff you can kind of do, get away with like comma, comma, comma, comma, and skip a bunch of stuff. You can't really do that here with PowerShell classes. At least you couldn't when I roll this. So you've got to pass in, if you don't want any of those values, you pass in a dollar or no. So instead of comma, comma, comma, comma, you've got comma, dollar, no, comma, dollar, no, comma, dollar, no. And that's ugly. But since we're wrapping commandments around this, we don't have to worry about that. Users aren't going to see that. But I have to assume that some of these are going to be no. So I just do a bunch of ifs and if the value is in there, I'm going to append it. And that's building the structure of that URL. Yes? You know, I don't remember, I don't know that you could when it first came out. Yeah. That might have changed. But yeah. Now, I could do more overloads if I wanted to make some other assumptions. I could skip that up. But again, since I'm not putting this in front of users, I'm not trying to make it pretty in this case. No, I don't understand. Yeah. It seems like that would be if that's supported, that would be easier. Yeah, potentially. Yeah. Yes? So you have the dollar and all, and you have your type string in full, it doesn't matter if you pass a dollar and all. It's not going to give you the type or math. Not any of the types that I've used. No. Yeah, I don't believe. Now, I think with maybe some of the enums, it might get a little weird. I wasn't able to really do anything with that. So yeah. In most cases, dollar and all can turn into whatever it needs to turn into. Okay. So the same kind of stuff. We're just building our URI string, and we're going to call invokeRest method and catch our return. Oh, one thing I've got in here, which has been proven really useful, is this line 108, this write verbose. So what I'm doing is I'm writing out the URI string that I built before I call it. It's really helpful for debugging. Interesting thing about write verbose, it works, but you can't call, there's no dash verbose switch on a class, right? But we're putting, again, we're putting commandlets in front of this. So when you call your commandlet with dash verbose or set your verbose preference, that carries through to the classes that it calls. So that works, and that's a handy, handy sort of thing. Now our output gets a little different on this one, because again, we are returning potentially multiple objects. Or multiple objects are coming back from our API endpoint. But we still can only do a single return. We can't return this output and then return the next one. You know, we don't get that pipelining operation that we're used to in PowerShell. So we've got to do the, you know, build an empty array and then loop through, create an object for each of our return fields, and we're just doing the same new constructor that we did before. And then concatenating them all together into an array of objects and then doing the return. And because, again, that ties in with our output type, that collection of DNSA records, because it's not a single DNSA record. And it won't let you, you build this array and try to return it as a single DNSA record. That's no good. You don't get to do that. So, but that collection of DNSA records is what sort of represents that array notation. So that's why that's important. So that's those two. Go through here, the set method. So those are our static methods. And those are our two methods of sort of creating an object based upon those API endpoints. Our set method is going to be, obviously, what we're going to use to change a value on an existing record. And because it has to be an existing record, this is not going to be a static method. This is going to be a method that exists on an instantiated object. So it's a little different. I've got, instead of static, I've got this keyword hidden. And that's handy for, well, that does what it says, it hides it a little bit from view. And I'll show you this in the demo. But basically what that does is that hides it from Gitmember and some of the IntelliSense stuff, not all of it. The idea is, because in our case, we are putting commandlets in front of the user. We don't really want the user mucking around with an object.set, an object.delete, right? We want them to use in our commandlets. That's the whole point. So you put a hidden there and it just doesn't show up in Gitmember. You can still force it and you can find it some other ways and you can still call it. But it gives it a little bit of a hidden. And that'd be nice if that were a little more strict in not letting you do that. But anyway, our output type here is void, meaning we're not going to actually output any objects. In our case, we're kind of, and again, this is a design choice. I've decided, the way I'm doing it is the idea of the set being I'm going to make a change and if it works, I've made the change. If it doesn't work, I'm going to throw all kinds of errors or whatever, but I don't necessarily need to return the object. In the commandlets, I add some switches. You can add like the pass-through and say, well, if I say pass-through, then spit me out the object. And if I don't just do it and don't tell me about it, without handling that in the commandlet, because again, that's a user choice sort of thing. So I'm going to do a set and I'm just going to not return any outputs. Same kind of idea, right? I've got properties and these are the inputs that I need in order to make a change to a record. So there's fewer here. Notice there's no name and no, there's no, well, there's no name on this one. Because this is a non, not a static method, I have the record and all the properties on the record that I can just reference with the dollar this keyword. So I can use all of this dot name to reference the name. I don't need to pass it in. The things that I'm passing in are the fields that the API allows me to change. So when I pass in, the IP address is going to be not the dollar this dot IP address. It's the IP address I want to change it to if I choose to change it. And same kind of thing, once I get my inputs is, and this is, you know, it gets kind of repetitive, which is nice when you're writing a lot of this because you can do a lot of copy and pasting. But same kind of thing, I build my URI string, I build my hash table for input and it can be JSON or it can be a hash table, either one. And then I call my invoke rest method. Now here, because I already have an object, and again because I'm not outputting anything, I simply need to change the properties on my existing object to, based upon my return. So in the particular case of my device, that unique immutable reference identifier, it turns out to not be all that immutable, so that can actually change from if you make a significant enough modification. So I am going to, I'm going to update that value. So I reference the dollar this here and you see I changed my reference string. It might not actually change, but I'm going to reference my returns. The return was that reference string, and that's what your API, again, your API defines all this, right? In the case of the get, it returned an entire object back. In the case of the create and the set, actually in the delete, it returns just that reference string. So I can use that reference string and then just make my modifications to my existing record. I don't have to output anything because it's making changes to my instantiated object in memory. So that's that, the delete method I saved for last because it's the simplest out of all of them. Just that you simply reference the URI of the reference string, that's how you reference the URI of an object and just call the delete method of invoke rest method in your delete method to find in your class. And that's really all there is to the delete method. That's pretty simple. Most APIs should behave that way. So that's kind of it for your really, your basic class definition. We're going to add a little flair to that in a second. Any questions on any of that particular stuff? Okay. So that's great. But maybe we want to add a little flair. Part of this was experimentation and all that. So let's look at inheritance. We can maybe save ourselves a little bit of typing. So I mentioned that this unique reference identifier is common among all objects in my API. The delete method, it turns out, is also exactly the same for every object because all I'm doing is referencing that unique reference identifier and calling a delete method. So because those are the same, and I know that they're exactly the same across everything, I can pull those out, put those in a root class, define a class that just has that one property and that one method, and then have all my other classes inherit from that. So my DNSA record can inherit from that root class. My CNAME record can inherit from that root class. I no longer have to then define reference string and delete method, which is, it's like five lines of code, but you get the idea. So take a look at what that looks like. Where'd I put that? That was in here. So I'm going to define a class called IB reference object. You know, of course, the IB is my prefix that I've used to make sure I don't step on anything else, and that's probably a good idea. I've read somewhere that's a good idea to do. So I'm going to define a class that, again, is not going to have much in it. I'm going to have a single property, my reference string, and I'm going to have a single method. And that method is just what we saw. It's exactly what we saw where, just using the underscore ref to an end of delete method. So, and then you've got some constructors. Every class needs constructors. It's a little complicated there. But where that gets cool is, you know, that saves me, that saves me my typing, right? So I can, I can then have a DNSA record that looks like this. So that notation at the top tells it to inherit from IB reference object. And you'll notice I no longer have, in my property list, I no longer have underscore ref, and in my methods, I no longer have delete. And that's great. That saves me a few lines of code. But hey, I've got, I've got this new class, right? It's IB reference class. Let's do some stuff to it to kind of spice this up a little bit. So again, here's a, here's a new reference object with some extra flair in it. We've got our underscore ref property, our hidden method. And if I, well, let's, let's do some extra work. Let's override or let's replace some other method. So we've got this two string method, which you've probably seen on objects all over the place. That comes down, I think, from system.object is where that inherits down, or somewhere up there, you get this two string method. And a lot of times that doesn't work so well on objects. If you've ever tried to out, dump out a big array of objects or a list out to Excel or something else, so CSV, you pull it up, and that column that you expected to have some information just has PS object, PS object, PS object all the way down. Because that property was not a string or whatever, it was itself an object. It's, didn't reference real. So, and that's because what that's doing basically is trying to convert it to a string and not doing a very good job of it. So we're going to replace the two string method. And so instead of getting system.or, in our case, IBDNSA record, we've decided to say that, you know, that, that unique reference identifier is a great textual representation of our object. It's got, and I'll show you that. I don't think I've actually shown you what it looks like, but it's got everything you need to understand what that object is. So in our case, that's a great, that's a better string version of our API. So we're going to add this in here. And because that's in here, every object that we inherit, that inherits from this base class is going to have a better two string property. That's really handy. So, okay, so enough of me rattling off, showing off my laundry here. Let's flip over to my demo and let's run some of these and see what this interaction looks like. Let's see, my stuff might have timed out a little bit. So I'm going to load up. Oh, I did this out of order. I was going to demo as I went. Okay, so we're going to do all the demos at once here. So I'm going to load up our first demo. This is the without inheritance sort of thing. I've got this, this, this web session here. So I'll talk a little bit about this real quick. Again, this is, this is separate from the, the sort of the class discussion, but it's important for APIs. You've got to handle your authentication in some way. And that's very much up to the API and how they, well, I can't get everything, is up to the API and how they implement that. In this case, I consider myself pretty lucky. I'm able to use the credential parameter and the web, or the session variable parameter of invoke rest method. And that's just save that session and you pass, pass a standard credential object. There's some APIs that you have to craft an authentication header and pass that every time. That's really hard. I haven't had to do that yet. So if you do my apologies or my condolences. So but again, that's, that's kind of a separate, how you handle that is kind of a little bit separate from this discussion. The assumption is that you will have done that at some point before you start mucking with objects. Now what I've decided to do, and this is just purely a design choices is take my, my session variable. And likewise this, this grid master, which is, is in my version, API version, which is used for building that URI string. And I've decided to scope those out to script scope, which scoping is really weird. But when you script scope, scope something from within a module that is available to the rest of the commands in that module, but not out to the user. And this demo case, I've script scoped it out to my session so we can take a look at it. So what that looks like is, is just your standard web session variable. If you used invoke web request or invoke rest method, you get this standard session. It's got your credentials and your cookies and all that sort of stuff. And these other ones are just strings. But those are, these are important for, well that one didn't go, but it's just, this is a URL for my, my endpoint, which is a live endpoint out in Azure. So that's kind of nice. And again, that's, I just mentioned that there because I'm, I'm going to be, I've handled that separately so that I can pass that into my classes. I didn't want to make design to show, design choices about authentication within my class. I kind of wanted that to be separate. Except to you how you want to do that. So let's take a look at what these classes look like. Again, this would have been, had I done this in the right order, this would have been before we talked about inheritance. So you see our get methods we've got our two. And this looks similar to what we saw in the definition. I see our first one gets by reference string and it just outputs a single record. And our second one is the query method. And it returns that, that collection object. So that's run through these. Let's do this. And we'll find out if Wi-Fi is working here and we'll get an object back. And I'm going to try to F8 my way out through all these demos so you don't have to watch me type. See how that works. So it's looking at what a record looks like, right? And we're just using the classes here. Notice that, notice that, that thing I've had to type there, right? So I pass in the parameters and on the right order and then a whole bunch of stuff I didn't want to search by. I pass in $1 and all. It's not the prettiest thing, but again, we're going to hide that. So, and I said, just looking at the classes. Let's look at our record here. Let's stretch that up a little bit. So it looks like your standard object is, it is. Nothing fancy there. It returns all our objects. And that's our unique reference string, right? It's got this big, long, unique value, two to the power of a million or whatever it is. It's got our entry name there. So that's what we chose to use as our textual representation of a record. So it's super useful, better than bracket object name. We look at get member. We will see this, kind of what we're expecting to see. You've got your methods and properties. You see the type name is IBDNSA record. Now you can do that, you know, if you had a PS object, you can add type names and all that kind of stuff. But because we defined this natively, relatively natively, we have that there. You'll see we've got all the properties that we had defined on the object. It's nothing new. You've got a two-strain get type, all your standard inherited down methods. We've got our delete method here. If you caught that when I was defining that, I put hidden on set. I didn't put hidden on delete. I wanted to illustrate that. So this is what that hidden keyword does, basically, right? So somebody's mucking around with their output and saying, oh, what do I have here? Oh, delete. That looks like fun. So it would at least hide that. Again, if they did record.delete and they knew what to type, they could still fill that in and cause trouble. But it hides it from the casual. Keeps honest people honest, right? So now that we have a record, let's look at our non-static methods that we can look at and I want to point these out because this is where things get a little weird. So record.set. Well, that's not our set method at all. That's not at all what we defined. That's some garbage. So what that is actually telling us, this is part of that weird thing with it being a set method. That's the set method on a DNSA record collection. What I need to reference is the set method and the delete method on an object. So I'm going to look at the first object in a single item array, our collection, and that is the set method that I expect. I don't know why that is. I'm sure there's someone here who can explain that. This is like some complicated developer stuff. So if you know the answer to this, come see me. I'd love to get more info. But that's something also to be aware of. But again, we're going to hide all that from the user so they don't have to worry about that and then delete is the same kind of thing. So I'm going to fast forward now. This is where we talk about inheritance. We're going to see some of the inheritance benefits in action. So let me load up those here. So let's go get a new record. We're going to do a couple things here. We're going to get another record. We're actually going to get the same record for server one. The same record I got for dollar record. I should have called that record one. That would have made more sense. Anyway, so I'm going to get that record. But again, this is the new class definition that I just created with inheritance. So it's the shiny polished one. And then I'm also going to get the same element using my, that's my new reference object, my base class. And you'll see what I'm passing in is that record two underscore ref. So I'm getting the exact same item from my APA endpoint, but I'm defining it as two different kinds of classes. So you can see what that looks like here. So I'm going to load this up, load that up. So let's look at ref record. And you'll see it only had, if you remember, it only had the one property underscore ref. And that's, so that's all we see. Pipe that to get member. Same kind of thing. I don't have any methods. I fixed that delete, put a hidden back on delete. So you don't see that, but I've got underscore ref there and that's it. My record two, which is the same object on the endpoint is of the DNS record class. So it has all the properties that we expect to see and that's all on how we defined it. If we look at record two, get member. So we've got all that. I fixed that delete, hidden, put the hidden back on the delete. So that's gone from there. And that looks just like record one. Now we added that two string, right? So let's take a look at what that looks like. And for this method, we have to reference the element within the array. So if we look at that, see now we get, or I'll show you in a minute, we get the, that reference string as our output. And that's a much better visual representation of a string object. So I have this here. So I want to point out that that is not a recursive sort of thing or retroactive, I guess. Record two was instantiated after we modified our class definition. A record was, is still that object we instantiated before we added inheritance. So if we look at it, it's two string. It does not benefit from that new and we get the old IBD and SA record. And so that's an important thing is it doesn't, it's not some sort of global all encompassing definition. What you define and when you instantiate that object, it is an object in memory like any other variable and properties and methods are defined on it at the time that you create that class definition or the time that you instantiate it. Okay. And now I'm back in the order that I was initially intending on going through. So let's look at what commandlets will look like wrapped around this, right? We saw this with that bracket zero bracket and the get method. Some of those are not the prettiest user facing things. If you're like me and you're the PowerShell guy in your office, you have a hard enough time getting people to run commandlets, much less getting them to run. No, you just run the class and make sure you put seven nulls and then you put the one and you'll be fine and that's never going to work, right? That's what I worked at. We never worked people. Just kind of go cross-eyed and probably pass out. So let's look at what we can do with commandlets. So we'll walk through these real quick and point out, again, like everything, these get kind of repetitive. But I want to point out a few things in my stuff here. So the get record, our get method we had two, right? So we had two methods of retrieving data. We have get a single record by this reference string and then query by endpoint for anything based upon all these different query parameters. So we're going to do the same thing in our commandlet, but do the parameter method. So we're going to do two parameter sets. One is going to be by ref and then one called by query, which you can call whatever you want, but that's the idea is we're going to make a, because we only want to call one method or the other depending upon what the user is looking for and that's where parameter sets come in. So we've got a parameter set that allows us to pass in our reference string and we got that down here, our underscore ref and then everything else is this by query. So these are all the properties we could potentially query by. I noticed none of those are mandatory because we don't have to query by anything. We run a get without any query strings or any query filters. We just get everything back. You'll see I've gotten in here at the top, grid master and credential and that's part of building that web session. So this is where I've put that rather than put it in the class, I put it in the commandlet. The idea here is, yes. That was a, that's the mode code that IntelliSense would not leave me alone unless I had both of those in there at some point. I think they fixed that, but I just haven't fixed the code. That's all that is. So I got, red curly braces was driving me nuts. So I just kept adding stuff and clicking the light bulb until it shut up and this is what I ended up with. So it works. I don't know what it's doing, but yeah. So what I'm doing here is allowing for a new web session, right? So you pass in your credential, you pass in the grid master that you want to connect to. The way I've set it up, and this is all purely design choice, right? I didn't want to type, I started out typing that every time. Every command I would run, I would add in credential and grid master. Tab complete is your friend. That got to be not good enough. So what I've done is, if a web session, and this is why I script scoped this stuff. So if a web session already exists in the script scope, I will just use it. And that's why these are optional. If there is not a web session or I pass in a grid master and a credential, it will create a new web session in the script scope. So the next command I run, I can leave those off. And that's been super helpful. I'll show you a little bit of that. But again, the authentication stuff is just weird. It's kind of separate from the class stuff. It's important, but you've got to, you know, a lot of it is design choice and how you want to handle that. So again, so we've got our pile of parameters. And again, we're doing, well, our pipelines for our reference string, because you have a pile of those you're trying to query. And so this is my begin block. This is the web session stuff. It's in all of these, but I'm going to only walk through it once. But here's the idea is, if I have a script scoped, or if I don't have a script scoped session, and I do have the grid master and credential parameters, I will create that web session. If I have nothing, then I'm going to bomb out and say you've got to log in first. Anyway, so that was how I chose to handle that. So I only have to type that once. Or, optionally, create that web session before I start doing anything manually with this new ID web session, which is an option, right? You can authenticate ahead of time and then run your commands. So I kind of merged all that into one big user-friendly thing. Which part of that thing, that separation of code is because this is the user-facing stuff, I'm free to add those kind of elements, which is the place for that kind of stuff in my thinking here. So not a ton of stuff, but that's just kind of a general bit of code. So if we look at our process here, right, it's really not much to it. I've got two parameter sets for my two methods, and if it's one parameter set, I call that method. And if it's the other parameter set, I call the other method. All the API stuff and all my return objects is all handled by the classes. And that's it. I could catch the return, and I could do some stuff with it. But it's already the kind of object I want to output anyway. So super short, super simple, which has kind of been my theme for all of this, right, keep everything nice and simple. I like simple code. It mostly almost fits on one screen, which is kind of my criteria for writing scripts. But the thing here is if you want to get complicated, if you want to add more complexity, more robustness to it, you're not starting with 200 lines of code that you're then trying to add functionality to. So I mentioned earlier, you know, if you double the API, is everything likes to return. Yeah, I love to return error 400 when anything isn't just right. So in the case of a DNSA record, if you're trying to get a query for a record in a zone that doesn't exist, you won't get that message, you'll get an error 400. If you're trying to query for, in my case, if I try to query for too many records and it's past what the API allows, it doesn't tell me that I've asked for too much, it gives me an error 400. So because I've got such a simple script here, what I could potentially do is add more code ahead of time. I could maybe check for the zone. If I define a IB DNS zone class, I can do a get on that zone property ahead of time and check that it exists. And if it doesn't exist, I can give much better feedback to the user about that and then maybe not even query for the A record. So but this is the place for that sort of stuff. This is where you could expand upon all that user-facing stuff. So that's super simple. Let's see, what did I put next? I think I put remove next because that was pretty cool. So yeah, remove. This is where we get into some of the parameter stuff that I talked about in the beginning. It makes it super simple, almost too simple. It kind of feels like cheating. So if we look at that, and you see I've got my support should process like every good delete method should probably have a double check. So here's what we're looking at. And this line here, this is really the key right here. It's a mandatory value and I simply say input a record that is a tight DNSA record collection because I want to allow for that multiple values to come in. And the main bit of this work is handled by that. I don't really need to do any parameter validation on that because I know the properties that are in that object. I know that it came from an API endpoint unless somebody's really messing with me. And so that's kind of it. I've got some extra parameters here because again, this is user facing and I'm trying to be a kindler, gentler, code developer. So helpful user-friendliness stuff. If you did pass in a reference string, say somebody hands you a CSV full of reference string, I queried these last week and I think they're still good. Can you go and delete all these? I'm pretty sure that they're valid. So I'm like, all right, instead of just saying no, go away and come back with an object, I'm going to get that for you. All right. So you can get any authentication information or if you already have it. And you give me a reference string. What I'm going to do is I'm simply going to call the get method with that reference string. If it gets an object, I just recall myself with that actual object. So that's just starting an extra bit. But that's the idea, right, is in your command lets you do all the user stuff to make things more user-friendly. So if I did pass an object, however, I have my should process, a good scripter, and I simply call the delete method of my class or of my input object has a delete method. And I pass the parameters and that's it. That's all I need to do. So I've got an entire removal command with just over 50 lines of code. So this would be a good place to put in extra validations and extra checks. And are you trying to delete a record? Are you trying to delete an entire zone or did you pass on a list of every record? So but you're starting from a simple point. So this is, it's easy to expand upon it instead of starting with this big mess. So this is one of the nice benefits is everything starts really simple. I'll do these real quick and just kind of cover. So set is a similar and kind of layout to delete or to remove, right? I'm going to expect an object or this or collection of this particular type of object. That handles all my validation. If you give me a reference string, I'll take that and I'll go and query for you to be nice. So I'm simply adding here is the parameters that I have chosen to change. So interesting thing here. So what I've chosen to do and I don't know if this is the best answer for this sort of thing, but it seems to work pretty well. I set these to default values that are unlikely. And then what I have set in code is if these values are changed from those unlikely default values, that tells me you want to modify that property. I started with like a PS bound parameters. The trick was though is first, again, this is a design choice, but so PS bound parameters, if I don't specify comment, it'll be no. It won't be in there, but what if I wanted to set comment to no? I can't pass no to comment because it'll just be no. So I'd have to add a whole set of parameters and say maybe a switch. Do I want to change the comment? Yes. Okay, well you want the comment to be. And then I've got 20 parameters instead of 10. So again, the design choice, this is the option I went with because I need to separate between a value, no, and unset. I've got kind of three states that I wanted to deal with. All right. So that was how I chose to do that. But again, the, where's my process block? Oh, there it is. That's weird. Okay, so the way this looks, same kind of thing. If you give me a reference string, I'll go do a query and get that for you and be nice. If you have changed any of these values, so this is where I check for that change from the unlikely value. If you have changed any of these values, I'm going to call a set. Now I chose to call a single set and this is mostly for simplicity, a single set method for each value. You could change the IP address and the comment and the TTL all at once. You could. I chose to do them individually. That's just purely a design choice for simplicity, right? To make that easy. I'd say we'll set the one or set the both or whatever. But again, that's kind of it. It does that set. I added the pass through and this is the place for that because remember the set, the way I wrote the class set does not output anything. It just does a set and then it goes on about its day. So I've added the pass through switch so it will return the input object as it's been modified if that's something you want. That's the place for that kind of thing, right? It's in the, in the commandlet. We're doing on time here. Should be good. Let's see. I think I had to create method which isn't terribly exciting. Blow that real quick. So we'll run some of these. Create method is similar to get. All right. You're just passing in optional authentication information. Name IP address. All that. Does that create? Yeah. And same thing. It's you simply pass in the parameters and then it runs the method and there's no, I didn't write any extra validation but again if you were going to add extra checks, right, if you're going to create a record into a zone that doesn't exist, you're going to want to check for that zone first probably. And do error handling and all that fun stuff. And this would be the place to do that because it's, it starts really basic which is kind of, I wanted to lay out this scaffolding of a super simple example and show how basic this can be and how easy the code can be. So those are the four commandlets. Oh and I was going to mention, you know, I chose to, again for simplicity, to do a single commandlet for a single operation on a single object which is why I have four commandlets for one class. You could do, if you had multiple classes, you could do one big complicated commandlet for all your gets and then do dynamic parameters to decide how your inputs are going to be and that's a big nightmare. But again, that's the thing with the code separation is once you've written your classes and you've defined that mapping to the API, how you call those classes is completely independent of that. You can have four commandlets, you could have one commandlet, you could have whatever. You can reference those from other commandlets, right? If I were going to call the zone class from my A record commandlet, I can do that because I've got those sort of elements defined and I'm not, if I change how I want to do this, I'm not rewriting everything. So then you get that functionality with helper functions or something like that but the idea of having things distinctly separated helps for code development. So let's switch back over to our demo. Any questions by the way? I know I'm kind of rattling through this kind of stuff. Maybe too fast. If I'm going too fast or a mumbling or whatever, just throw something out of your hauler. But I've got a lot of stuff to kind of go through. Oh, I was just to hide that. I was just to see that. Okay, commandlets. So that was my thing. There we go. So let's look at what our end result is, right? We've found our classes and we've done all that work for our endpoint and we've written our commandlets to be a better interface. And so we're going to expect commandlets to behave like commandlets, right? So I'm going to do a query. Let's see. Let's see. Let's see. Let's see. It's up a little bit. Now in this case, I did a query for name server one. I actually have this on my pre-stage. It's like a cooking show. I pulled something out of the oven that's already been cooking. I've got two records here, right? I've got two records for server one. They've got different IP addresses and different comments. But it's two records. So if I saved this to a variable, it would just be that array of objects. Just like any other commandlet when you output, I could pipe that to, actually let's do that, right? If I did, pipe it to a table. You get a table view, right? So all the stuff you expect from objects, that all works the same. So let's do an additional query. Remember, we can query from multiple values simultaneously. So we're going to get server one, the other one, not both. So that's that one record. And that returns. And again, behind the scenes, this is still that collection of records because of the method that we chose. It's got only one element in it. But the users don't care about that. I get an output. You know, I could pipe that to FT just as well, or output CSV or whatever. And that's cool. So let's create a new record right quick, just so we can mess around with that. And that works exactly, again, exactly how you would expect that to. I got my should process. So I get kudos for writing good codes. You should always have one of these, by the way. If you're writing commandlets that do anything. And there we go. It creates a record and outputs it and just looks like everything else. It's an object of that type. So that's fine. Let's call some damage. Let's do some pipelines. So I'm going to do my getDNSA record to server one, which remember there are two of them. I'm going to take each one of those, pass it across the pipeline. This is like any other commandlet that you would write. But this is kind of what this looks like. So I get a should process on the first record. Not on the whole thing, just on the first record. And I've got my target, and it gives me the two string representation of the record instead of just do you want to do a remove on DNSA record? That would be less useful. But here I've got the actual object. So that's much more useful. For a lot of use cases, I'm going to say yes to that. And then I get my next one. Passes across the pipeline, just like you would expect any array pipeline operations to do. And that's it. So that's all kind of standard stuff. Same with set here. Let me run this. Nope. I don't want to move that down. There we go. So if I run this, I get that server two record. It's got a comment of second record. Very unique and useful. If you saw that in production, you'd be very happy that comment was there. So I'm going to change that comment to something that's arguably not much better. So same thing. I'm going to pipe, just like remove, pipe it to DNSA record. The only thing I have to specify is the comment I want to change it to. Everything else comes across the pipeline in my piped object. I get my should process. And now if I look at my object, I have my updated comment. So that all works exactly how you would expect commandlets to work with not a lot of extra work and in my opinion, a much cleaner sort of structured way of putting your code together. It's readable. It's understandable. Anything you could put a script on one screen and not have to scroll through a million. Of course, I could probably add more comments and furboss logging and all that kind of stuff. That would make it a little bit bigger, but it starts out very simple. We'll get on time there. So any questions about any of that stuff before I flip to the other half? No, okay. You're all either asleep or interested. One of the two. Okay. One o'clock. Yeah, okay. So that's great. And you're going to do this and in my case, I've got at this point with my one object, I've got a single class definition and four commandlets. If you like me and you like to separate all your scripts out into individual files for ease of coding is what you see. You've got something like this. And then you're going to add to kind of do a rinse and repeat operation. You're going to go hit the CNAME record next. You're going to do the same kind of thing with changing properties around for CNAMES are obviously a little bit different. And you're going to hit PTR and then you're going to do a fixed address and then you're going to do, you know, whatever IPAM or DHCP leases. There's no shortage of objects on your, on a typical API. So when you're done with that, you're going to have a folder full of stuff. And again, what we talked about is this is, that's great. But what we want to do is make that useful for everyone. So let's talk about now how we're going to go from a folder full of scripts and class definitions, PS1s to ultimately a module published on the PS Gallery that anyone can download, import, use, update. Let's back and do some PowerPoint because that's what we're all here for. I can do my fat magic, hot keys here. All right. No, maybe. There we go. Okay. That's where we left off. So let's talk about the release pipeline. That's my son enjoying his own release pipeline. So release pipeline, there's nothing better than just sitting back and watching a release pipeline just go. It's just, anyway, enough of that. It's not recorded. You can cut that out. Okay. So release pipeline, you hear people talk about it. Build, test, deploy or source, build, test, deploy. So we're looking at that. We want to do that. And of course we want to automate everything. So let's look at what that might mean in our case. So build. What I think in on build is at the end of a build, let's create something. Let's create a tangible thing that I can interact with. So I'm thinking I'm going to make a module. So I'm going to create my directory structure. And next one's kind of contentious. I like to concatenate all my script files into one big PSM1. I know people like to dot source. That's cool too. That's what you want to do. I don't know if one's better than the other. I like to argue about that on the internet because it's the internet. I might make some change or create or make some changes to my module manifest. If I'm going to go to the gallery, I'm going to need to have all my metadata for that, versioning information would go in there. If I'm using external help, if I'm using markdown, I'm going to turn those into that horrible worse XML, worse than XML mammal thing. And I'm going to use platypus because I don't want to do that by hand. So if I'm using inline help, that wouldn't be something I would need to do. But those are the steps I would define and at the end of my build, however I've chosen to do it, I have a module and a folder that I could take and copy and give to someone and they can import and it works just like any module would. So that's my build. Deploy is pretty simple, right? I want to deploy it to the gallery. And since I've done all my work and build, that's actually relatively trivial. But between those two things, because again, I want to automate this and I don't want to deploy a bad change, I'm going to do a test. So I'm thinking, okay, I'll fire up Pester and do some tests and because I want to do these in an isolated fashion, thinking, okay, well, everything is local until invoke rest method. And invoke rest method is where I leave and I go out to my endpoint and do stuff. When I'm not going to assume I have an endpoint, it occurred to me that running a bunch of zone and record deletions and creations on my corporate DNS appliance would probably be a bad thing. So I thought I should mock that and save my job. So that's where I started. I'll just mock invoke rest method, catch whatever comes from my, when my class calls invoke rest method, my URI string and my payload, figure out what I need to, if that worked and then return an appropriate output that my code was expecting and then do sort of an end to end test. So the problem with that is what you're really saying there, the short version of what I just said was I'll just reverse engineer my API. How hard could that be? Impossible, it turns out, unless you have access to the source code. So I actually went down that route and write a bunch of code and I'm parsing URIs and if it's a get method, I look for this syntax and if it's a post, I'm looking for this syntax and doing my hash table and all this kind of stuff and I got this big block and I thought to myself, you know, it's probably the first law of unit testing that if your test code is more complex than the code you're testing, then it's not good test code and that was where I was at. And it turned out it didn't work anyway because once I went actually to an actual appliance, I guessed wrong on my reverse engineering and it didn't work anyway. So that's generally a bad idea. If you want to break this down and do more inputs and outputs before and after invoke REST method and do true unit tests, you could probably do something like that. But what I wanted was my end-to-end test because I've got to run this against an endpoint anyway. So mocking didn't work. So if you can't beat them, join them. And I sat in a dark corner and cried for a little while and rocked back and forth and threw away all my PowerShell books, my T-shirts and then I thought to myself, what does everyone do when they have a problem they can't solve? Where did I read in the CIO magazine that all problems are solved? The cloud will solve all our problems, right? Microsoft tells us that every year. So I went to the cloud and it actually worked in this case. So what that means in this case is what I'm going to do in the cloud is build an endpoint, a new endpoint, a fresh endpoint, my endpoint for my, in my case, my appliance. I'm going to build a brand new one while my test against it because it's empty. I set it up the way I wanted it, it's mine, it's real, it's live and I control all the variables for that. So that was kind of the idea. And there's two things you need when you want to do that. I'm going to walk through kind of the overview of that here. You need, because again, we're doing this in a programmatic fashion, we do this automatically. I'm not going to be manually building anything in the portal. So we need an ARM template. I'm also not going to be manually logging into the portal to run said ARM template or doing any interactive login for PowerShell at all. So we need some non-interactive authentication information. So that is kind of the, that's the goal, right? And we're going to integrate that into our release pipeline and our build test deploy. So this is kind of what we end up with. You see I've got our build and our test deploy, right? These are our kind of our dependent code blocks. And then I'm going to stick build test environment right before test. And I'm going to add some extra stuff clean, it's just mousekeeping. And it is going to be our login. We're going to do our login there. Because really, if the login doesn't work, there's no sense in doing any of the other stuff, right? If we can't create our tests, there's no point in doing a build. So I'm going to run all this from AppVare, was my choice. And so what you end up with with AppVare, and I'll walk through these a little bit. AppVare uses a YAML file to give it instructions. You tell it what to do and you tell it what to run and set some variables and all that. And that just calls AppVare PS1 script. And then that calls your Saki. Saki is a DSL that allows you to kind of define dependent code blocks in a nice readable fashion. By the way, if this model looks familiar at all, this is all from Warren Frames blog a few years back on PSDeploy and release pipelines for PowerShell. I totally stole a lot of his code and then added some stuff to it. So if you see him, thank him. If you read that blog, I actually got, at the end, I've got a link to it. That was a really good way to kind of see an end-to-end process and get an idea of how a release pipeline should work. So if you're not already super familiar with release pipelines and all the steps, that was a great thing. So I took that and added some stuff to it. That's what we do here is take someone else's stuff and add onto it. So that's out there. You could do the same kind of thing with Jenkins or with Squid or Octopus or whatever, or potentially locally, right? Maybe I'm doing local development on a new feature. Maybe I'm adding the scene record. And before I go production and maybe accidentally deploy an unfinished thing to the gallery, I want to run some local tests. So maybe I'll add the well-named build.ps1. And that's just going to be a local script that will allow me to call the exact same production script. So I'm going to run the same build. I'm going to run the same tests, which is handy when I'm doing development on a new feature on this other branch. I'm not going to accidentally deploy it. I'll put some checks in there. But maybe I want to make sure that, okay, if it passes all these tests locally on my PC, it's still going out to the same endpoint. There's no reason why it shouldn't work when AppFair runs it. So that's kind of the idea. So let's take a look at what that looks like. And again, this is all, this is the optional part of the presentation because you can really do this however you want it. You could add eight steps or two steps or 15 or whatever. You can branch off and come back and do, sake allows you to do all kinds of really complex. But again, my theme is simplicity. So I'm just going to go in a simple sort of fashion through all of this stuff here. So let me switch over to that. That is over here. Oh, wait, no. So before I do that, that's right. I was going to talk about Azure a little bit. And promise this isn't a pitch. I hit the cloud pitches, but in this case it works. I'm going to kind of walk you through. If you're cloud averse like I was, it's handy to see this. So we need two things. That's the next thing we're going to do. I mentioned we need two things when we're dealing with Azure in an automated fashion. We need an ARM template, and then we need some sort of login information. So I'm not going to talk about ARM templates because I totally cheated. I went out to the portal, click, click, click, built my endpoint, which in my case it turned out to just be a VM with a custom image that this vendor created. It can be as complicated as your API dictates, right? It has to be some sort of weird cluster with this web front end. If you have to define all that, you go out there and you build all that. I built all that from hand, put it, give it a public IP address so I could access it, verified that it worked, and then I just did a snapshot. It's changed since I did it, but they make it super easy. Actually, let me just show you that here. They make it super easy to grab that ARM template. The ARM template that they write. Oops, just getting in the way here. There we go. So this is my test bed for this session. So in my case, I've just got, oh, this is just a pile of my resources. I've got my virtual machine, and then of course that has a network adapter and a storage account and a public IP address. That's obviously important so I can get to it. So I just come up, once I get that and I verify, manually verify that that's all working and accessible from anywhere, I can just come over to my automation script, this tab they added, and it writes the ARM template for me. It's not the prettiest code in the world, and you'll probably want to do some cleanup, but once you get it working, you don't really have to muck with that any ideally. So that's the world's shortest ARM template demo, but you can dig more into that. I think there's some sessions on ARM templates that go into a whole lot more detail on that. What I want to talk about in Azure is the second half, the authentication piece. That's kind of weird. So what I'm basically saying is I want to allow an application to authenticate automatically like a user. In the old days of AD, we would call that like a service principle. I want to authenticate as an application. You kind of have that here. So what we're going to do, we're going to go to our Azure Active Directory, and you don't need a complicated, fancy corporate P2 whatever. See, I'm using my on Microsoft, which is the free one they gave me when I logged in, and it's a free trial. Oh, what did I click on? Oh, I went back. So I'm going to come in here and what they call app registrations. I'm going to create a new web app to do my login. This gets in all kinds of weird OAuth and application. We're not going to really do any of that. We're just creating an application so we have a security context on which to authenticate. Let's come in here, a new application registration. Again, what we feel in here matters very little, and you want to give it something that you can find later. I've done this a few times. We'll call this InfoLogstiness Appliance Rest Test, something unique. Sign-on URL again, because this isn't a real web app that we're authenticating to, or test call is calling it, but we're not authenticating it. Sign-on URL doesn't really matter. You can put whatever. You can put local host. I like to put my GitHub repo for the project just to help me remember what I was doing when I see this later. I don't even think that's right, but anyway, it doesn't really matter. It just has to be a URL, because again, we're not going to use that. What we are going to use, well, we got to go find it. It's too much to ask for them to give it to us. So we'll search for it. Nope, that's not my app, apparently. Someone else's? I've done this a few times. I got some mess. What did I call it? This one here. So what we are going to use is this application ID. That is going to effectively be our username for our credential object, application ID. So we're going to copy that off, and just throw that in a notepad or whatever for later use. We'll save that. Now we need a password. We've got a username. We need a password. We're going to go to settings, go to keys. That's the GUI part's almost over. So we're going to create a new password object. Just call it whatever. We'll call it password, because we're super creative. Duration of how long you want it to last. We'll just say never. And then value will be displayed on save. So we're not going to specify the password. We're going to let Azure pick it. So we're going to save this, and it's going to give us this password. Notice it says you won't be able to retrieve this later, so you'll want to memorize that immediately. Or I guess we could, for demo sake, we'll just copy it out. But I would totally memorize that if I wasn't doing a presentation. So you're going to save that off. I'm going to memorize it. So now we've got our username and password. And we can stick that out and out there. We can stick that in wherever. Of course, it's a username and password, so we're going to talk about how to obfuscate that. We don't want to put that in our source control. That's on GitHub, or Azure login on GitHub is probably a bad idea. So that's enough of the fancy web interface. And that's great. But anything, just about anything you can do through the Azure portal, you can do through PowerShell. So let's do it through PowerShell because that's way more fun. So I've already done the interactive login here. I think unless it timed out, I mean we'll find out. So when you do these, we're going to do the same thing. We're just going to create an application registration username and password. In this case, rather than have the portal create password for us, we're going to specify it. So we're going to make a nice long password with a special character and uppercase, create a secure string from it. And we're going to call this new Azure RMED application. And same kind of stuff that we filled in, right? We give it a display name. We give it a URL. And in this case, we give it the password rather than asking us to specify it. But we're going to run that. Let's see if that's still logged in. Oh, that's right. I keep doing this over and over again. Let's change this a little bit. Again, the name doesn't matter. Oh, why not? I didn't clean that up. I got to change this, too. It's a problem with demos. They make a lot of garbage. There we go. So now we've got an application. Let me look at it. And it's just an object with some stuff in it. So that is the PowerShell version of all the pointy clicky stuff that we did. And this is better. But they match to the same thing. You've now got an application ID. And it's got a password. So you've got the makings of a credential object. So let's go through and let's do the next part, which would be to how you would actually use that to log into Azure. So we could use the information that we memorized. I mean, wrote down. But we're not. We've already got it in PowerShell here. So we're just going to use that. So we're going to create your standard PS credential object. No big deal. We're using the application ID, not GUID, and the secure passwords. Our application ID is our username. So we're going to create that. And when you do this here, you have to get the tenant ID. And there's a couple different ways of getting that. You can write it down. I heard this actually a website called, like, what is my tenant ID? And you give it a URL and it'll tell you, this is your tenant ID. Or this is their tenant ID or whoever. So that's kind of cool. We're going to use this Git Azure RM subscription. And that's just a way of getting that. We just need that to tell it where we're logging into. And then we're going to use log in Azure RM account. We're giving that credential object. And the tenant ID, we're telling it this is a service principle log in so it doesn't try to do any interactive nonsense. And that is just a trivial thing. It runs that. And then it logs in. It spits us back our information saying we're logged in. So this is the bit of code that we put in our build pipeline. We obfuscate our password, obviously. But this is the code. This is completely non-interactive. So I use the appvair encryption. Yeah. So you're just saying, yeah, I just go in and if we have time, I'll show you that. But yeah, that's super simple. So yeah, I just use that. When I'm doing the local build script, it's not appvair. I just build all those passwords ahead of time and then pipe them in through parameters. Yeah. Let's see. Where was I on that? Yeah. So I'm going to walk through what this kind of looks like just to kind of give you a look at how that looks. So I'll start with appvair. So this is the appvair.yaml file. If you haven't seen this, let me expand all my stuff here. Yeah. Well, I'm going to expand it all. So the yaml file is just a basic instruction set for appvair to kick off. When you create an appvair account, you tie it to your GitHub account. And so I'm giving you a couple things here. I'm telling it, if I have a commit message that says, update it, read me. Don't do a build. I don't need you to do anything. Just go back to sleep. I'm only doing a build on the master branch so I can do commits to any other branch without worrying about it kicking off a deploy. And then the rest of that mostly is environment variables. That's kind of its method of passing information along to the build server because this is all running on their build server, which is just a PowerShell session out on their server. So I've got my module name and my resource group, and that's used when I'm developing, you know, spinning up my Azure environment. I've got an admin password, which of course I want to obfuscate. So this is what that looks like. You get this secure string. And appvair provides that. It crypts it with your account so it can always decrypt it. And it does a pretty cool thing when you use this secure password string method. When it runs its build system and if it uses that password in any sort of visible way, it keeps track of that and it'll obfuscate so you get a bunch of stars on the output screen unless you do like write out whose password or something stupid. But it'll protect its, it keeps itself from screwing up and then, you know, lets you kind of do your own thing. But it takes a little effort. So, you know, I've got all kinds of stuff encrypted. I encrypted my tenant ID just for fun and my app login. That's one I already made, obviously. My PS Gallery key, of course, is important. You don't want anyone deploying as you. So we just add all those in there. But yeah, you have unencrypted variables, encrypted variables, they're treated the same way. $e and v colon, these all show up as environment variables when you're running your scripts. You just treat them like environment variables. And then I'm telling you to just run this test script, right? You can do a build script and a test script and you can let appvair handle those stages. I'm just calling one script and letting Socky handle all my ordering of my steps. You can do that kind of whatever you want. So that's going to call appvair PS1. All I'm kind of really doing here is setting some variables to other variables. The reason I'm doing that is I don't want to be everything to be, I don't want my Socky script to be tied directly to appvair, right? Because again, I might use Jenkins or something or my local build script. So this is kind of a shim script because I don't want to reference appvair build folder in Socky because I can't use appvair build folder if I'm not using appvair. So I have project root and artifact root and module version. I import some modules that Socky might need to run. Obviously I need Socky installed to run Socky in Pester and Platypus. So I just install some modules because again, at the end of the day, a build system is just a PowerShell environment spun up that you're running scripts against. That's where they all build system is. So I'm going to call invokeSocky and give it my script. It's going to walk through that. This is what appvair is running. And the way this is laid out, oh, that didn't minimize. That was going to do a whole big, pretty thing here. Let's see. I didn't do it. I don't know. I'm second. I got this new extension called fold. It's really cool for auto folding stuff except when it isn't, apparently. So and I wanted to kind of have this view because I want to show the layout. So domain specific language uses this concept of a task, right? So they do a sort of an inverse thing. Default always runs. The default depends on deploy and clean test environment, which is my last one. Of course, deploy depends upon test. So it's going to run test first. And test depends on build, et cetera. So it runs in a top-down fashion from a bottom-up dependency. Kind of weird. But that's kind of the idea of Socky. And you can do branches and you can do all kinds of extra dependencies. I didn't need anything too complex. And for a basic PowerShell module function, you don't need anything too complex. So let's see. So I mentioned I do my login there. Oh, no. I'm going to use my hotkey here. So I check if my build system is AppVayer. In my case, I'm doing AppVayer and local. So if I'm doing a local build, I'm just going to use an interactive login. So if my build system is AppVayer, I'm going to do my non-interactive login that we just saw with our credentials. And notice I'm referencing Azure App login ID as an environment variable. That was encrypted. But throughout there, I don't need to do anything special to tell it to decrypted. It just handles that. Same with my password. It just handles that. And then this is what we just saw. We log in with a service principal log in. I do that first because I don't want to bother doing anything else if that fails. It's all going to go south from there. Clean is just some, when I'm running iterations with a local build, you know, you got to delete some folders. So let's look at build. Build is, that's what I was talking about with, I want to build a module. So this is all my code to turn a pile of PS1s into a usable module. A couple things in here. One that's really been useful is the versioning. So AppVayer will give you a build number. And that's an exposed variable environment underscore build, build numbers and all like that. And that will increment every time a build runs. So what I'm going to do is I'm going to use that build number in my module manifest as the version number. Just automatically increment. So that way every time I do a deploy to the gallery, it goes up by one. If I want to change my major, minor version manually, if I'm, oh, this is so much better than the last one, I'll make it 1.2. You know, I can do that. But this helps me not worry about unincreasing or having, you know, versions step on versions. That's all just automatic. So that pretty works. If I ever get to whatever they, whenever that number rolls, I don't know what's going to happen. I don't know if that rolls at 99 or 999. I don't know. But I'm only like 50. So that should last me for a pretty good while. So that's one of the things I'm going to do with my update module manifest is I'm going to take, I've got a static module manifest that has everything you need to run that module. It's pretty much, most of that information is static, right? I update the copyright because it kind of feel like I should. I don't know. And then I have some variables there. The module version is really the only thing I'm changing here is I'm going to increment that based on my build number. So when I deploy it again, it's going to show up as new. Which PowerShell Gallery likes that sort of thing and PSGit likes that sort of thing. And this is where I'm going to do my platypus, right? So I create my new external help. If you're doing, you know, external help or markdown help, that's important. So that's all kind of done. And when that's, if that works, it's okay, looks at errors. If any of these bomb out, throw an error, the whole thing will stop. The dependency sort of depends upon build, but really depends upon build not failing. That's what it's actually saying in the short end. So next I'm going to build my test environment. And that is my Azure deploy, basically. So I've already done my login, which presumably if I've gotten at this point that passed. So build test environment is two things, really. I'm creating a new resource group based on an environment variable I set with a location that I set there. And then I run through, there's this command, it doesn't do a whole lot, test azure rm resource group deployment. Those names, jeez. That's going to do some basic validation on your ARM template. Give you a reasonable chance that it might work. So errors still get through, but that's something worth running. So again, I'm checking that that passes. And then I'm going to run my deployment. Now I have this ARM template that I downloaded that I let the portal write, which is just a big steam and mess. Everything's a parameter, it's like 800 parameters, and they're all static values anyway. It's ugly, but it works. So you can clean that up if you want or not. Just close your eyes and pretend it's not too bad. So I'm going to pass the resource group, obviously, to put it in. There's my template file, my azure deploy JSON. I just save that in my source control there. There are any numbers of parameters I could pass to it. The only thing that I want to pass to it, really, because nothing really changes, but I do want to pass my password to it. Because again, I don't want to put a password in my ARM template any more than I want to put a password in the rest of my code. So I'm going to pass that password, and I'm going to let AppVare decrypt that admin password. And that's going to happen all kind of behind the scenes. So that's going to deploy my azure environment, and that takes about eight minutes. So that's not bad. It's going to look for that status, that provisioning state. So if that's succeeded, it's then going to continue on. If that fails, of course, it's going to stop because there's no reason to run tests against an environment that doesn't exist. My tests are just your standard Pester tests. And so that's just going to gather some data and run invoke Pester. And the way I've organized the tests, I won't go into that because we don't have three hours. I wrote a lot of tests. It's kind of embarrassing. But the idea there, because I now have a full environment, I can do full tests. I don't need to test this piece or this piece. I can just say, create a new DNSA record with the name of server one. Get the DNSA record. Is the name server one? Okay. Good. Is the IP address what I said it should be? Good. Okay. Now take server one and delete it. Did it delete? Yes. So I test the creation, and then I can test the deletion. In between that, I can test all the querying. There are two server ones with an IP address of whatever. And again, I don't have to do anything really complicated because I just have an environment to run against. I'm not really doing weird unit tests. I just run the full thing. Okay. And so that's that. And then deploy is really, is pretty straightforward. We've actually made it this far. Publish module. That's really it. Right? I've got my module name. It's in my environment. And then my API key is my encrypted string that I let out there and crypt for me. That's really how it is. And then because I'm a cheapskate, I added clean test environment. And that's going to go back to Azure like two minutes later after my tests are run and delete everything. Well, delete everything in that resource group, not everything else. And that's really cool. Not to get all weird and evangelize-y, but basically what I've done there is created an environment automatically, run 400 tests or some un-gully amount of tests against it in like three minutes, and then delete it. And so what I pay for is that three minutes of runtime, which they usually round down to nothing, which is nice. Yeah. So that's kind of that. How are we doing on time? I started this late. When is this in? 12.45? Okay. So yeah, we're about on track. So any questions on any of that stuff? Okay. Now, a lot of this is up to you how you want to do it. And it's very much up to design choices. I kind of wanted to point out, you know, kind of walk through and end-to-end things. Just seeing that from start to finish is, for me, it was really helpful to see that I didn't really get the individual pieces until I kind of saw how it all walked through. So let me show you. Sorry. Yes? Can we switch to the AppVayer? Yes. I was actually going to show you that next. Yeah. We got a few minutes. So let's see AppVayer. Now, that was super simple. It's almost disappointingly so. So I'm going to AppVayer. I've logged in. You can link it with a number of different accounts. I've linked it to my GitHub account. That seemed to make it sense. So this is me logged in to AppVayer. And I'm looking at, in this case, I'm looking at my latest build. Let me show you this here first here. So this is a history of all the builds that have been run. And you see over here it's got, it has the build number. So it's pulled in the 1.1 and the 1.2 is for me. I put that in my module manifest. Actually, I think I put it in a variable first. It came for me somewhere. And I had that. And so I control the incrementing of those codes. But then you see AppVayer is doing 60, 61, 62, 63. So I'm just grabbing that. It's handling that incrementation. The message here is just whatever my commit message happened to be for that master branch. So you can see here I added an IB lease object to handle DHCP leases and define them as a class so then I incremented that. So if we look at one of those here, we'll look at this latest one here. Again, because all a build system is, it's just someone else's PowerShell session running your scripts for you. And so what it does, the first thing it does is it, any build system will do this. It uses get to clone the repository. So it gets a fresh copy of all the data in a local folder. And then it has its YAML that calls the PS1. The output of this is really all up to you. This is probably one of those times when it's OK to use write host because you're not outputting to anything but the screen here. You're not going to pipe to anything. So I have these lines, right? It just outputs lines to separate the sections. Ideally, you'd put in some good information about where it was in the process. I didn't do any of that. So there's not a whole lot in here. So but in it, I would do a login and I would probably say logging into Azure. Login has succeeded. But this is, Sake is doing this. So Sake is telling you executing the init task that is defined in that block. And clean executing build. So there's some output I just didn't catch again. This is just sort of, go away a little. So this is again, if you're running Sake locally, this is what you would see. Just like on the build system, this is what you see. It's just a PowerShell session. So you can see not a whole lot of output when I run my tests. Here's where you see all my Pester tests. So I guess I could talk about these real quick. So I've broken these up into sections, right? So for each object I've created, like in this case, I've got this web session test and this IB view. This is an infobox construct. But I've got networks and DNS zones and DNS records. So you can see I'm doing, I've got this, the way I did it to save myself from typing is I do a, in order to do a remove test, I need an object. And in order to do a get test, I need an object. So I'm going to run all my create tests first. I'm going to do two things with that. I'm going to create all the objects so that I can use them later. And I'm actually going to test that they were created correctly. So I do my create tests for each of my records. And then I'm going to run my queries, query against all the different kinds of permutations of how to find stuff. And then at the end I'm going to run all my deletes. And I didn't write really fancy Pester tests. So I just wrote a lot of them because that should be the same thing, right? So and then I wrote some more and then I got tired and then I wrote some more and then I deleted some stuff and I wrote some more and then my test complete. And then at the end, this is all soccer here. Soccer gives you this little report. And so it'll tell you how long each section took. So you can see it, the build test environment, about eight minutes to deploy to Azure. My tests took about just under four minutes to run. And then I did my clean. Deploy takes no time at all and then clean. So that's soccer handling most of that output. And I've various just sort of running that on your behalf. Jenkins and all those other ones, they probably work in a similar sort of fashion. Let me show you while we're in here. I think we've got a few minutes. The encryption thing is so you go to your account because it's based upon your account and you have all your stuff for your standard account stuff. They'll let you give them money if you want to. But you don't have to. This is all free. I should mention that. The app there thing for basic setup is completely free. So they have this section here, encrypt data. So you're going to hear, and this is just encrypting it for your account. So for any project that you have, this works. You just put in whatever, a nice secure password that no one would ever use, encrypt it, and it spits out that encrypted value. And it also shows you the syntax for your YAML. So you just copy and paste that, stick that straight in your YAML, probably change the name of the variable. You wouldn't want to have more than one of those. But yeah, so that's really all there is. App there makes that super simple. If you're going to run a local build system, obviously you wouldn't use those. That's going to be unique to App there. But that's where that shim script comes in, right? You're going to pull those in and then set the values to something that you expect to be more modular. Let's see. That, I believe, is, well, maybe. Okay, come on. Yeah, so I like to do all that again. Okay. That's, I guess, the review. That is what we talked about. I'm going to walk through the build script. It's basically the same kind of thing as the App there script. It's just setting some variables and taking more interactive input. And that's it. That's what I have. I am now exhausted and passed out on the couch. I've got a couple of links up here. The top one is the link to this presentation. That repository has this slide deck. It has the demo code and all that kind of stuff. The next one is the actual project that I based this on. So if you actually have an Infoblox and you care, check that one out. It's on the, it's actually on the gallery as, I think I called it Infoblox Command. There's a few of them out there. But if you want to check out the larger set of code, Infoblox Classy is where that's at. That last link is Warren's blog on the PS deploy. It's a little old. I mentioned to him yesterday, I was like, hey, I loved your blog. I'm going to actually use that website and come in. So, I don't know, take it for what you will, but he had some links in there to some other work and kind of how this release pipeline lays out. And it was really helpful for me to kind of understand and I based a lot of what I did off of that. So I want to include that link. So that's it. Any questions? We're finishing a little early, but yes. I have a little novice question. What did that Sel and the class come up with? That's the problem with something like this is because the classes have methods, APIs have method, invoke rest method has methods, so it's hard to talk about because it gets muddled real quick. But anyway, so that's it. That's all I have, I guess. We can be the first ones in the lunch line, so that's cool. Thank you all very much. Please. Thank you.
 Morning everybody, my name is Jeremy Murrah and I'm here to talk about, I need a deep  breath to say all that, building commandlets for REST API with PowerShell classes, Azure  based integration tests and continuous deployment.  They gave me a double link session so I'd have time to read all of that.  And the reason there's so much up there is rather than do a deep dive on a single topic  like Kirk's debugging session or some of the other ones, I wanted to kind of cover an end  to end scenario, like a project you might do, where say you start with this super important  REST API that you need to wrangle, that's not swagger based because you can just let  that write its own code, but say you've got this API, they didn't write their own PowerShell  commandlets for whatever reason, they think it's a fad and we're all going to switch back  to Perl or something.  But you've got this API and you're going to go through the process of turning that into  something that's a lot more user friendly, but you're going to keep going.  And ideally, ultimately end up with something that you publish out on the PowerShell gallery  so all the rest of us can use it and then I'll have to go through this process.  So I kind of wanted to touch on all the pieces of that that you might see.  So we're going to go through all of that.  This was the specifics for my demo code and my project was something I did with, it's  a device called Infoblox, that's the company name, I forget the appliance.  If anybody is familiar, it's a DNS, DHCP, IPAM kind of appliance.  So yeah, we have that, they have an API, they're sticking to their Perl API, they're sticking  to their guns.  So they have a REST API and all that.  So I kind of went through a process of writing commandlets for that and just kind of kept  going and it's out on the gallery and that was super useful.  So some of the stuff is specific to that but I'm going to try to separate out the specifics  of that implementation and point out what's generic to this kind of structure as a concept  that you can reuse for something else.  Anyway, so that's the idea.  So let's do that.  So what a REST API is, well it's a lot of things but at its real basic core, it's a  URL representation of an object or multiple objects and then you've got your standard  methods that you can perform against it, right?  Your HTTP calls, your git, your post, your delete, you know, pretty basic stuff there  and there's some extra, some functions and extra, but that's the general idea.  Classes are a similar kind of thing.  You've got a representation of an object with defined properties and then these static  methods.  And looking at this, there was, seemed to be, you know, a fair bit of carryover, kind  of a one-to-one mapping.  So that's kind of the genesis of where this idea came from.  That and also PowerShell 5 had just come out and I was looking for an excuse to play with  classes because why not, right?  So this kind of started in there.  But why classes?  You can do this with helper functions or you can just do this, you've always been able  to do this with standard PowerShell.  We're just doing invoke REST method after all.  Well, so like I mentioned, part of why I started was really just to kind of play with it, right?  And try something new that hadn't been done before as far as I knew.  What I found was a couple of really interesting sort of benefits to it when I started playing  with it.  What, you know, we talk about in PowerShell, the thing is always have a function that does  one thing, right?  And that's for readability and that's for ease of use, ease of coding later when you're  trying to figure out what you did.  And so that's a common model and that's really helpful and when I started looking at doing  with the REST API and putting a commandlet in a way, even unlike a get, you know, get  some data of this particular type, you're kind of doing two things.  You can think about it.  You've got your invoke REST method and you've got to build that URI string or that payload  and do all this work to parse that data and then send it off and then get your return.  Hopefully instead of a 400 you get an actual return and then you've got to do something  to maybe parse that output or do some logic around it.  So that's all the API interaction code.  Then you've got your parameters and your input that you're taking from the users and any  output you send them and maybe some extra validation, all that user friendly, gooey,  touchy-feely stuff.  So in a way those are kind of two separate things.  And you can do it all together.  It's not terribly complicated but what I found is if you set your class definitions as kind  of that API interface.  So you're going to make an object and that object is going to be that direct representation  of your API object.  And all of the work that you do when you are connecting to that API and that representation,  that's all going to happen in your class structure.  It's a whole separate file or a whole separate bit of code.  And that frees you up in your commandlet to focus on all your touchy-feely user stuff,  your parameter validation, maybe some extra logic, error handling.  APIs love to give you that 400.  Any little thing wrong and you get a 400.  There's an error buried in there somewhere.  Now that I'm an expert on debugging from Kirk's class, I might figure all that out.  But it's difficult.  So maybe you do some extra logic ahead of time.  And because you've separated out some of the API work, and we'll see this in some of  the code, it makes it really simple to sort of expand upon that because you've got that  separation.  So I thought that was pretty interesting.  I'm going to start going through this.  Another thing that's really cool is kind of what you get for free with PowerShell.  You know, PowerShell loves objects.  We all know that.  And when you do objects in and objects out, you get a lot of stuff for free.  Right?  You get all that pipelining and you get stuff like FormatTable and FormatList.  That all comes for free.  Well, PowerShell really likes typed objects, objects of certain type.  When you can tell it just something other than a PS object, you get even more for free.  So on the left is something you might do.  Let's say you have a, you've got a commandlet that you're going to do a set.  So you've already written the commandlet that retrieves some data from your API or from  some other source.  You want to, because this is PowerShell, you want to pipe that directly over to your set  command.  And what you want to do without any, you know, too much fuss, but if you're passing in a  PS object, you want to have a, make some assumptions about that input, right?  You need to know that, okay, I'm expecting that this PS object is coming from my get  command, but I can't guarantee it, right?  A PS object can come from anywhere.  You can craft it by hand or it can come from import CSV.  You don't really know.  So maybe I'm going to use the value from pipeline by property name and I'll say, okay, I expect  that my input object should have a name property and maybe I've got a numerical value and a  string value.  So I'm going to define all these because I'm going to, I'm going to need that data anyway,  but also I want to make sure that my object is the right kind of object.  All right, so maybe it passes all these checks and I didn't put all this, this would have  been a horrible slide, but maybe it passes those checks.  But you still want to understand that this is a valid representation of a live object  on your API endpoint, right?  It may be that kind of PS object, but that could have been something somebody dumped  out to a spreadsheet a month ago.  And now they're telling you, hey, go clean this up.  So you can't assume that it's valid.  So you'd probably have to go after all of this and maybe a begin block or, well, I guess  it'd be your process block is go out to your endpoint and do a retrieval anyway and validate  that, that object that you're going to act upon is really out there and it really has  all this data.  Especially before you're doing something like a remove.  You really want to make sure you're removing the right thing.  So you've got to do a lot of work in your command let just to validate your data.  Well here on the right, if we have our API object defined from a class structure, we've  got a very specific object type.  And so we can specify that on our single input object.  We take the entire object in, it's of this certain type.  It will only pass our parameter validation if it is of that type.  And what that tells us or gives us an assumption, we wrote that class definition so we know  all the properties.  It has to be of that type.  We know that or we can have a good enough assumption that it is a valid object on our  API endpoint because it's of fewer places that that class can come from.  It's going to come from our class definition that did that call and retrieved that object.  So this is not a security boundary by any means.  Somebody can just make a class themselves and if it was an intent to be malicious, that  is still going to happen but that's not the point of parameter validation.  This is the save you from causing horrible harm when you're passing in the wrong kind  of thing.  So you get a lot of that kind of stuff for free because you've defined the object of  that type.  So you can turn a whole lot of code into one line in a way and that, again, frees you up  to do more of the user-friendly GUI stuff.  And then, of course, because this is PowerShell and we all like having fun, why not?  You never know if something is a fit for the job until you try it.  That's my five-year-old doing some experimentation of his own.  They said we can't use copyrighted images but I figure I own the copyright on that,  both the picture and the subject.  So experimentation, that's why we're here, right?  So that's enough slides for a little while.  Oh, that next is a good one, yeah.  So let's switch over and...  Oh, wait, I was kidding.  There we go.  Let's look at some of this here.  Let's see.  Where am I at?  I should have loaded all this up ahead of time.  There we go.  So I'm going to walk through this here.  Let me hide this.  Is size okay?  You guys see that in the back?  Okay.  It's horrible up here, these tiny screens killing me.  Okay.  So this is your basic, what a class might look like.  Like I mentioned, I'm doing my use case here as a DNS appliance.  So I picked the DNSA record.  Everyone's favorite granddad, the DNSA record, to represent with this class.  And so you have your...  The kind of properties you'd expect on a record, right?  A name and an IP address.  We get some extra stuff and some of the...  Again, this is all the API specific stuff, your properties, and that's going to be defined  by your API and hopefully your documentation is really good because that's what makes or  breaks an API.  So they have defined this object as having these properties and I'm using those here.  So we've got a comment is something the InfoLux gives us this ability to put a comment.  I've got this...  Under score ref is really interesting, that's a unique identifier for every kind of object  that the API endpoint gives us.  So every A record, every CNAME, every address reservation has a different one of those.  And I found out you can actually start a variable with an underscore.  So that's pretty interesting.  It makes for some funny looking code, especially when it's a property and you've got something  dot underscore, but it totally works.  That was kind of interesting.  So anyway, you've got all your properties that would define whatever the object is going  to be.  And then you've got your constructors here, which is really just like a new method, right?  This is how you, when you are instantiating an object from this class, these are the properties  that you need to build the object.  And you can do some really complex things here or not.  So we've got to just pass in the properties that are defined at the top.  Say you must fill in all of these in order to create an object of this type.  That just gets interesting, is in the methods, right?  Let me just take a sit here.  So we talked about, you know, an API has your crud, your create, your read, your update,  delete.  Those are primarily your four methods of interacting with an API.  So we're just going to represent or create a method, a one-to-one method for each of  those.  All right?  So I'm going to walk through these a little bit.  I'm trying not to be too tedious with all the code.  But so a method, I like to think of a method for me anyway as kind of a little mini function  within my class, right?  So I've got all these properties here.  And these are not, they're similar to the properties that we define in the class, but  they're not really related.  These are the properties or the inputs that I need in order to execute this method.  And that's it.  The fact that they are similar to the class makes sense because I'm creating a DNA record.  But I could have a method that took completely different inputs depending upon what I want  to do.  It's a little bit of separation there, but thinking about it like a little function inside  your class is kind of how I like to think about it.  So a couple of things here.  We look at line 14, right?  This is the beginning of that.  So this is a static method, meaning I don't already have to have an object defined in  order to run this method, which makes sense for a create.  I'm not going to create an object upon an object that I've already created.  I'm going to create an object out of nothing.  So we've got a designation here, IBDNSA record.  That's the name of my class.  So what I'm telling it here is the output from this method is going to be output an  object of this type.  And that's super important for the parameter validation stuff when we get to the command  list, what I'm showing there.  It's important.  The object output type is really important.  You could have, you know, system.object and you wouldn't lose any of the benefits, but  that would work.  And then the name is whatever you want to call it.  It doesn't have to match up with any sort of API verb or anything like that.  It's just whatever you want to call it.  So the general flow, the body of these, of all the methods are pretty similar.  The idea here is you're taking these inputs and doing whatever processing you need to  build your payload.  And that payload is your URI string, which is that, you know, the URL, the reference to  where that object is on your API endpoint.  And then optionally, some sort of payload, some sort of body, right, JSON or hash table.  You know, that's optional because in case of, and we'll see with like a retrieval or  a get or query, you can do all of that on the string.  You don't need a payload at all.  For something like a put or a post, in this case, it's good to have.  So that's, and that's kind of the pattern for all of them is, that's, and this is all  the work you would do if you were doing just doing this in PowerShell without classes is  you would just go through the logic of building that data set and then you just call invoke  request method to submit it, hopefully it passes and you get a return object and then  doing some work upon that return object, depending on what that return object is.  So, in this case, for, you know, different, on my API endpoint, different object or different  methods return different results.  So in the case of a create, it doesn't actually return to me back my entire record.  It returns back that unique reference identifier that I mentioned.  This is this big long string.  It's like a GUID without dashes, basically.  So it returns that.  Now what I need to do, and Snowvers talked about this a couple times, where the return,  the way you output things from classes is very different from, from Standard PowerShell,  right?  Standard PowerShell, you just kind of have to throw it out there to the universe and it'll  eventually plinko down and it'll end up out on your screen or in whatever your output is.  You don't do those classes.  You have to define one and only one and not less than one output objects.  And everything has to return an object, a single return.  So you've got to define that and that's, that, that takes some getting used to.  But it's nice because you don't accidentally end up with extra output.  You know, if you're ever trying to use return and then you have some, something that you  didn't catch and it spits it out to the screen, you end up with some garbage output.  So you can't do that with classes, but you do have to be aware.  So what I'm doing, what I've chosen to do here, and this is just a design choice, what  you ultimately need to do because of the output we define is you need to craft an object  of this type, IBD NSA record, through some method, craft that object and then that's  your return.  So what I could have done here is done the colon, colon, new and called the constructor  because I've got all the information I need, right?  I've got all these properties that came in to my method.  I could just build that up.  What I've chosen to do instead is call my next method, the get method, just kind of an extra  check.  So I'm going to send that information that it gave me.  In that case, it's that unique reference identifier.  I'm going to submit that back to my end point and say, hey, I know you just created this,  but can you give it to me again?  And when I get that back, the way it's defined, the get method on my API endpoint is defined  is to return the entire object.  So it's kind of an extra validation for all my properties.  If it decided to change the name just slightly or on its end or change my, say my IP address  or something, I get all that by just doing that query again.  So it's an extra check and it doesn't cost me anything.  And then this is also that important part.  If I don't get any return back, if it fails or whatever, I have to return something.  In this case, I'm just going to return $.  But you have to have a return statement for every end point.  There's this nice useful error that'll say something, IntelliSense will pop it up and  it'll tell you something like all code paths, don't return an output or something like that.  So that's super important.  So that's kind of a create method.  And I'm going to walk through get next because I want to point out how I've used that.  And with get, you have actually a couple of different options.  This expansion here.  So we actually have two get methods we're going to go through because there's two different  ways you might do stuff.  So this is the get method that I just, I call it from create, right?  I've got my unique reference identifier and I'm just going to pass that back to my endpoint.  And I've got some extra properties here and I'm going to talk about authentication very  briefly because that's very API specific and how you want to handle that.  But I've got this, I've got my session which is the web session that I catch from invoke  rest method.  And that's a nice and easy way of doing authentication.  If your API lets you get away with that, then you're in good shape because that's a nice,  there are harder ways to do authentication.  So I'm passing my session in and I've chosen to, and this is a design choice.  It's arguable where you want to put that.  But what I've chosen to do is rather than do any authentication in my methods, in my  classes, is handle the authentication and creating that session outside of that and then just  pass it in.  So, you know, six to one half a dozen.  It saves me a little typing doing it once ahead of time.  And when I get into the command, I'll kind of show you the choices I made there.  But anyway, so I'm going to pass in that session is required obviously to have a connection  and then there's some information I use to build my URI string that reference identifier.  You can see here I don't have a hash table or a body of any type.  This is an option with certain methods you can just query.  And again, this is up to your API.  But I can just do a single URI string to get my return.  And in the same way as the create method, I call invoke rest method, get my return object  back.  But in this case, I'm using that constructor, that new method to craft my object.  And I have to do some formatting on the IP address.  But using all my return, my return object properties, I just build that object and then  I return it or return no.  Same kind of rules apply.  I've got to return something.  So and that's great for when you have that unique reference identifier.  But most of the time you don't, right?  You're, especially if your API didn't have that.  If you are trying to get say every DNS A record for this one server or everyone that has this  IP address because maybe I've got some duplicates, right?  You're going to need to do a query, which is going to be your common way of interacting  with an API.  So this would not work at all for that, right?  I'm not retrieving, I'm not going to have any of the inputs that I need coming into  my method at all to even do a query.  So I'm going to make an entirely new method.  What they call an overload is what the developers like to call it.  So which ultimately is just another method with the same name.  And so these are, I'm doing the same thing.  I'm doing a get, but I'm doing it in a completely different way with a completely different set  of inputs.  So rather than try to just have one big one and do if this and that, I'll just make a  whole separate one.  So it flows more logically because it's a different kind of operation.  It starts out similarly.  You see this is also a static method because again we don't already have an object, we're  getting a new one.  Now there's a little thing here with the output.  Notice it's the IBD NSA record, but it's that collection notation, that extra set of braces.  And that's important because in this case we are returning potentially more than one  object, right?  Because you do a query, with that unique reference saying you're only getting that one record  with this, you're potentially getting more than one.  And because of the way classes handle outputs and because of the way we want to be able  to do parameter validation when we're wrapping commandlets around this, that's super important  to have.  It's got some weird little quirks.  I'll show you that in the demo, it gets kind of weird, but that's important to have.  And again it's a git method.  So it's the same kind of thing.  And you'll see our properties, we've got a whole lot more coming in, right?  We've got our name and our p address and comment and zone and all these are all things that  we could potentially be querying upon.  And that's defined again by our API documentation.  They have said that we can search by all these things.  If they didn't allow us to search by something, we'd just leave it out.  So there's a little more work going on here, but it's the same kind of thing.  We're taking all those inputs and just building up, in this case just building up a very long  URL, adding each property.  Now the interesting thing with classes and why we have this laid out the way it is is  none of these input properties are optional.  You have to put something in there.  You know, with some of the.NET stuff you can kind of do, get away with like comma, comma,  comma, comma, and skip a bunch of stuff.  You can't really do that here with PowerShell classes.  At least you couldn't when I roll this.  So you've got to pass in, if you don't want any of those values, you pass in a dollar  or no.  So instead of comma, comma, comma, comma, you've got comma, dollar, no, comma, dollar,  no, comma, dollar, no.  And that's ugly.  But since we're wrapping commandments around this, we don't have to worry about that.  Users aren't going to see that.  But I have to assume that some of these are going to be no.  So I just do a bunch of ifs and if the value is in there, I'm going to append it.  And that's building the structure of that URL.  Yes?  You know, I don't remember, I don't know that you could when it first came out.  Yeah.  That might have changed.  But yeah.  Now, I could do more overloads if I wanted to make some other assumptions.  I could skip that up.  But again, since I'm not putting this in front of users, I'm not trying to make it pretty  in this case.  No, I don't understand.  Yeah.  It seems like that would be if that's supported, that would be easier.  Yeah, potentially.  Yeah.  Yes?  So you have the dollar and all, and you have your type string in full, it doesn't matter  if you pass a dollar and all.  It's not going to give you the type or math.  Not any of the types that I've used.  No.  Yeah, I don't believe.  Now, I think with maybe some of the enums, it might get a little weird.  I wasn't able to really do anything with that.  So yeah.  In most cases, dollar and all can turn into whatever it needs to turn into.  Okay.  So the same kind of stuff.  We're just building our URI string, and we're going to call invokeRest method and catch  our return.  Oh, one thing I've got in here, which has been proven really useful, is this line 108,  this write verbose.  So what I'm doing is I'm writing out the URI string that I built before I call it.  It's really helpful for debugging.  Interesting thing about write verbose, it works, but you can't call, there's no dash verbose  switch on a class, right?  But we're putting, again, we're putting commandlets in front of this.  So when you call your commandlet with dash verbose or set your verbose preference, that  carries through to the classes that it calls.  So that works, and that's a handy, handy sort of thing.  Now our output gets a little different on this one, because again, we are returning potentially  multiple objects.  Or multiple objects are coming back from our API endpoint.  But we still can only do a single return.  We can't return this output and then return the next one.  You know, we don't get that pipelining operation that we're used to in PowerShell.  So we've got to do the, you know, build an empty array and then loop through, create  an object for each of our return fields, and we're just doing the same new constructor  that we did before.  And then concatenating them all together into an array of objects and then doing the return.  And because, again, that ties in with our output type, that collection of DNSA records,  because it's not a single DNSA record.  And it won't let you, you build this array and try to return it as a single DNSA record.  That's no good.  You don't get to do that.  So, but that collection of DNSA records is what sort of represents that array notation.  So that's why that's important.  So that's those two.  Go through here, the set method.  So those are our static methods.  And those are our two methods of sort of creating an object based upon those API endpoints.  Our set method is going to be, obviously, what we're going to use to change a value on an  existing record.  And because it has to be an existing record, this is not going to be a static method.  This is going to be a method that exists on an instantiated object.  So it's a little different.  I've got, instead of static, I've got this keyword hidden.  And that's handy for, well, that does what it says, it hides it a little bit from view.  And I'll show you this in the demo.  But basically what that does is that hides it from Gitmember and some of the IntelliSense  stuff, not all of it.  The idea is, because in our case, we are putting commandlets in front of the user.  We don't really want the user mucking around with an object.set, an object.delete, right?  We want them to use in our commandlets.  That's the whole point.  So you put a hidden there and it just doesn't show up in Gitmember.  You can still force it and you can find it some other ways and you can still call it.  But it gives it a little bit of a hidden.  And that'd be nice if that were a little more strict in not letting you do that.  But anyway, our output type here is void, meaning we're not going to actually output  any objects.  In our case, we're kind of, and again, this is a design choice.  I've decided, the way I'm doing it is the idea of the set being I'm going to make a  change and if it works, I've made the change.  If it doesn't work, I'm going to throw all kinds of errors or whatever, but I don't necessarily  need to return the object.  In the commandlets, I add some switches.  You can add like the pass-through and say, well, if I say pass-through, then spit me out  the object.  And if I don't just do it and don't tell me about it, without handling that in the commandlet,  because again, that's a user choice sort of thing.  So I'm going to do a set and I'm just going to not return any outputs.  Same kind of idea, right?  I've got properties and these are the inputs that I need in order to make a change to a  record.  So there's fewer here.  Notice there's no name and no, there's no, well, there's no name on this one.  Because this is a non, not a static method, I have the record and all the properties  on the record that I can just reference with the dollar this keyword.  So I can use all of this dot name to reference the name.  I don't need to pass it in.  The things that I'm passing in are the fields that the API allows me to change.  So when I pass in, the IP address is going to be not the dollar this dot IP address.  It's the IP address I want to change it to if I choose to change it.  And same kind of thing, once I get my inputs is, and this is, you know, it gets kind of  repetitive, which is nice when you're writing a lot of this because you can do a lot of  copy and pasting.  But same kind of thing, I build my URI string, I build my hash table for input and it can  be JSON or it can be a hash table, either one.  And then I call my invoke rest method.  Now here, because I already have an object, and again because I'm not outputting anything,  I simply need to change the properties on my existing object to, based upon my return.  So in the particular case of my device, that unique immutable reference identifier, it  turns out to not be all that immutable, so that can actually change from if you make  a significant enough modification.  So I am going to, I'm going to update that value.  So I reference the dollar this here and you see I changed my reference string.  It might not actually change, but I'm going to reference my returns.  The return was that reference string, and that's what your API, again, your API defines  all this, right?  In the case of the get, it returned an entire object back.  In the case of the create and the set, actually in the delete, it returns just that reference  string.  So I can use that reference string and then just make my modifications to my existing  record.  I don't have to output anything because it's making changes to my instantiated object  in memory.  So that's that, the delete method I saved for last because it's the simplest out of  all of them.  Just that you simply reference the URI of the reference string, that's how you reference  the URI of an object and just call the delete method of invoke rest method in your delete  method to find in your class.  And that's really all there is to the delete method.  That's pretty simple.  Most APIs should behave that way.  So that's kind of it for your really, your basic class definition.  We're going to add a little flair to that in a second.  Any questions on any of that particular stuff?  Okay.  So that's great.  But maybe we want to add a little flair.  Part of this was experimentation and all that.  So let's look at inheritance.  We can maybe save ourselves a little bit of typing.  So I mentioned that this unique reference identifier is common among all objects in my API.  The delete method, it turns out, is also exactly the same for every object because all I'm  doing is referencing that unique reference identifier and calling a delete method.  So because those are the same, and I know that they're exactly the same across everything,  I can pull those out, put those in a root class, define a class that just has that one  property and that one method, and then have all my other classes inherit from that.  So my DNSA record can inherit from that root class.  My CNAME record can inherit from that root class.  I no longer have to then define reference string and delete method, which is, it's like five  lines of code, but you get the idea.  So take a look at what that looks like.  Where'd I put that?  That was in here.  So I'm going to define a class called IB reference object.  You know, of course, the IB is my prefix that I've used to make sure I don't step on anything  else, and that's probably a good idea.  I've read somewhere that's a good idea to do.  So I'm going to define a class that, again, is not going to have much in it.  I'm going to have a single property, my reference string, and I'm going to have a single method.  And that method is just what we saw.  It's exactly what we saw where, just using the underscore ref to an end of delete method.  So, and then you've got some constructors.  Every class needs constructors.  It's a little complicated there.  But where that gets cool is, you know, that saves me, that saves me my typing, right?  So I can, I can then have a DNSA record that looks like this.  So that notation at the top tells it to inherit from IB reference object.  And you'll notice I no longer have, in my property list, I no longer have underscore  ref, and in my methods, I no longer have delete.  And that's great.  That saves me a few lines of code.  But hey, I've got, I've got this new class, right?  It's IB reference class.  Let's do some stuff to it to kind of spice this up a little bit.  So again, here's a, here's a new reference object with some extra flair in it.  We've got our underscore ref property, our hidden method.  And if I, well, let's, let's do some extra work.  Let's override or let's replace some other method.  So we've got this two string method, which you've probably seen on objects all over the  place.  That comes down, I think, from system.object is where that inherits down, or somewhere  up there, you get this two string method.  And a lot of times that doesn't work so well on objects.  If you've ever tried to out, dump out a big array of objects or a list out to Excel or  something else, so CSV, you pull it up, and that column that you expected to have some  information just has PS object, PS object, PS object all the way down.  Because that property was not a string or whatever, it was itself an object.  It's, didn't reference real.  So, and that's because what that's doing basically is trying to convert it to a string and not  doing a very good job of it.  So we're going to replace the two string method.  And so instead of getting system.or, in our case, IBDNSA record, we've decided to say  that, you know, that, that unique reference identifier is a great textual representation  of our object.  It's got, and I'll show you that.  I don't think I've actually shown you what it looks like, but it's got everything you  need to understand what that object is.  So in our case, that's a great, that's a better string version of our API.  So we're going to add this in here.  And because that's in here, every object that we inherit, that inherits from this base class  is going to have a better two string property.  That's really handy.  So, okay, so enough of me rattling off, showing off my laundry here.  Let's flip over to my demo and let's run some of these and see what this interaction looks  like.  Let's see, my stuff might have timed out a little bit.  So I'm going to load up.  Oh, I did this out of order.  I was going to demo as I went.  Okay, so we're going to do all the demos at once here.  So I'm going to load up our first demo.  This is the without inheritance sort of thing.  I've got this, this, this web session here.  So I'll talk a little bit about this real quick.  Again, this is, this is separate from the, the sort of the class discussion, but it's  important for APIs.  You've got to handle your authentication in some way.  And that's very much up to the API and how they, well, I can't get everything, is up  to the API and how they implement that.  In this case, I consider myself pretty lucky.  I'm able to use the credential parameter and the web, or the session variable parameter  of invoke rest method.  And that's just save that session and you pass, pass a standard credential object.  There's some APIs that you have to craft an authentication header and pass that every time.  That's really hard.  I haven't had to do that yet.  So if you do my apologies or my condolences.  So but again, that's, that's kind of a separate, how you handle that is kind of a little bit  separate from this discussion.  The assumption is that you will have done that at some point before you start mucking  with objects.  Now what I've decided to do, and this is just purely a design choices is take my, my session  variable.  And likewise this, this grid master, which is, is in my version, API version, which is  used for building that URI string.  And I've decided to scope those out to script scope, which scoping is really weird.  But when you script scope, scope something from within a module that is available to  the rest of the commands in that module, but not out to the user.  And this demo case, I've script scoped it out to my session so we can take a look at  it.  So what that looks like is, is just your standard web session variable.  If you used invoke web request or invoke rest method, you get this standard session.  It's got your credentials and your cookies and all that sort of stuff.  And these other ones are just strings.  But those are, these are important for, well that one didn't go, but it's just, this is  a URL for my, my endpoint, which is a live endpoint out in Azure.  So that's kind of nice.  And again, that's, I just mentioned that there because I'm, I'm going to be, I've handled  that separately so that I can pass that into my classes.  I didn't want to make design to show, design choices about authentication within my class.  I kind of wanted that to be separate.  Except to you how you want to do that.  So let's take a look at what these classes look like.  Again, this would have been, had I done this in the right order, this would have been before  we talked about inheritance.  So you see our get methods we've got our two.  And this looks similar to what we saw in the definition.  I see our first one gets by reference string and it just outputs a single record.  And our second one is the query method.  And it returns that, that collection object.  So that's run through these.  Let's do this.  And we'll find out if Wi-Fi is working here and we'll get an object back.  And I'm going to try to F8 my way out through all these demos so you don't have to watch  me type.  See how that works.  So it's looking at what a record looks like, right?  And we're just using the classes here.  Notice that, notice that, that thing I've had to type there, right?  So I pass in the parameters and on the right order and then a whole bunch of stuff I didn't  want to search by.  I pass in $1 and all.  It's not the prettiest thing, but again, we're going to hide that.  So, and I said, just looking at the classes.  Let's look at our record here.  Let's stretch that up a little bit.  So it looks like your standard object is, it is.  Nothing fancy there.  It returns all our objects.  And that's our unique reference string, right?  It's got this big, long, unique value, two to the power of a million or whatever it is.  It's got our entry name there.  So that's what we chose to use as our textual representation of a record.  So it's super useful, better than bracket object name.  We look at get member.  We will see this, kind of what we're expecting to see.  You've got your methods and properties.  You see the type name is IBDNSA record.  Now you can do that, you know, if you had a PS object, you can add type names and all  that kind of stuff.  But because we defined this natively, relatively natively, we have that there.  You'll see we've got all the properties that we had defined on the object.  It's nothing new.  You've got a two-strain get type, all your standard inherited down methods.  We've got our delete method here.  If you caught that when I was defining that, I put hidden on set.  I didn't put hidden on delete.  I wanted to illustrate that.  So this is what that hidden keyword does, basically, right?  So somebody's mucking around with their output and saying, oh, what do I have here?  Oh, delete.  That looks like fun.  So it would at least hide that.  Again, if they did record.delete and they knew what to type, they could still fill that  in and cause trouble.  But it hides it from the casual.  Keeps honest people honest, right?  So now that we have a record, let's look at our non-static methods that we can look at  and I want to point these out because this is where things get a little weird.  So record.set.  Well, that's not our set method at all.  That's not at all what we defined.  That's some garbage.  So what that is actually telling us, this is part of that weird thing with it being a  set method.  That's the set method on a DNSA record collection.  What I need to reference is the set method and the delete method on an object.  So I'm going to look at the first object in a single item array, our collection, and that  is the set method that I expect.  I don't know why that is.  I'm sure there's someone here who can explain that.  This is like some complicated developer stuff.  So if you know the answer to this, come see me.  I'd love to get more info.  But that's something also to be aware of.  But again, we're going to hide all that from the user so they don't have to worry about  that and then delete is the same kind of thing.  So I'm going to fast forward now.  This is where we talk about inheritance.  We're going to see some of the inheritance benefits in action.  So let me load up those here.  So let's go get a new record.  We're going to do a couple things here.  We're going to get another record.  We're actually going to get the same record for server one.  The same record I got for dollar record.  I should have called that record one.  That would have made more sense.  Anyway, so I'm going to get that record.  But again, this is the new class definition that I just created with inheritance.  So it's the shiny polished one.  And then I'm also going to get the same element using my, that's my new reference object,  my base class.  And you'll see what I'm passing in is that record two underscore ref.  So I'm getting the exact same item from my APA endpoint, but I'm defining it as two different  kinds of classes.  So you can see what that looks like here.  So I'm going to load this up, load that up.  So let's look at ref record.  And you'll see it only had, if you remember, it only had the one property underscore ref.  And that's, so that's all we see.  Pipe that to get member.  Same kind of thing.  I don't have any methods.  I fixed that delete, put a hidden back on delete.  So you don't see that, but I've got underscore ref there and that's it.  My record two, which is the same object on the endpoint is of the DNS record class.  So it has all the properties that we expect to see and that's all on how we defined it.  If we look at record two, get member.  So we've got all that.  I fixed that delete, hidden, put the hidden back on the delete.  So that's gone from there.  And that looks just like record one.  Now we added that two string, right?  So let's take a look at what that looks like.  And for this method, we have to reference the element within the array.  So if we look at that, see now we get, or I'll show you in a minute, we get the, that  reference string as our output.  And that's a much better visual representation of a string object.  So I have this here.  So I want to point out that that is not a recursive sort of thing or retroactive, I guess.  Record two was instantiated after we modified our class definition.  A record was, is still that object we instantiated before we added inheritance.  So if we look at it, it's two string.  It does not benefit from that new and we get the old IBD and SA record.  And so that's an important thing is it doesn't, it's not some sort of global all encompassing  definition.  What you define and when you instantiate that object, it is an object in memory like any  other variable and properties and methods are defined on it at the time that you create  that class definition or the time that you instantiate it.  Okay.  And now I'm back in the order that I was initially intending on going through.  So let's look at what commandlets will look like wrapped around this, right?  We saw this with that bracket zero bracket and the get method.  Some of those are not the prettiest user facing things.  If you're like me and you're the PowerShell guy in your office, you have a hard enough  time getting people to run commandlets, much less getting them to run.  No, you just run the class and make sure you put seven nulls and then you put the one and  you'll be fine and that's never going to work, right?  That's what I worked at.  We never worked people.  Just kind of go cross-eyed and probably pass out.  So let's look at what we can do with commandlets.  So we'll walk through these real quick and point out, again, like everything, these get  kind of repetitive.  But I want to point out a few things in my stuff here.  So the get record, our get method we had two, right?  So we had two methods of retrieving data.  We have get a single record by this reference string and then query by endpoint for anything  based upon all these different query parameters.  So we're going to do the same thing in our commandlet, but do the parameter method.  So we're going to do two parameter sets.  One is going to be by ref and then one called by query, which you can call whatever you  want, but that's the idea is we're going to make a, because we only want to call one method  or the other depending upon what the user is looking for and that's where parameter  sets come in.  So we've got a parameter set that allows us to pass in our reference string and we got  that down here, our underscore ref and then everything else is this by query.  So these are all the properties we could potentially query by.  I noticed none of those are mandatory because we don't have to query by anything.  We run a get without any query strings or any query filters.  We just get everything back.  You'll see I've gotten in here at the top, grid master and credential and that's part  of building that web session.  So this is where I've put that rather than put it in the class, I put it in the commandlet.  The idea here is, yes.  That was a, that's the mode code that IntelliSense would not leave me alone unless I had both  of those in there at some point.  I think they fixed that, but I just haven't fixed the code.  That's all that is.  So I got, red curly braces was driving me nuts.  So I just kept adding stuff and clicking the light bulb until it shut up and this is what  I ended up with.  So it works.  I don't know what it's doing, but yeah.  So what I'm doing here is allowing for a new web session, right?  So you pass in your credential, you pass in the grid master that you want to connect  to.  The way I've set it up, and this is all purely design choice, right?  I didn't want to type, I started out typing that every time.  Every command I would run, I would add in credential and grid master.  Tab complete is your friend.  That got to be not good enough.  So what I've done is, if a web session, and this is why I script scoped this stuff.  So if a web session already exists in the script scope, I will just use it.  And that's why these are optional.  If there is not a web session or I pass in a grid master and a credential, it will create  a new web session in the script scope.  So the next command I run, I can leave those off.  And that's been super helpful.  I'll show you a little bit of that.  But again, the authentication stuff is just weird.  It's kind of separate from the class stuff.  It's important, but you've got to, you know, a lot of it is design choice and how you want  to handle that.  So again, so we've got our pile of parameters.  And again, we're doing, well, our pipelines for our reference string, because you have  a pile of those you're trying to query.  And so this is my begin block.  This is the web session stuff.  It's in all of these, but I'm going to only walk through it once.  But here's the idea is, if I have a script scoped, or if I don't have a script scoped  session, and I do have the grid master and credential parameters, I will create that  web session.  If I have nothing, then I'm going to bomb out and say you've got to log in first.  Anyway, so that was how I chose to handle that.  So I only have to type that once.  Or, optionally, create that web session before I start doing anything manually with this  new ID web session, which is an option, right?  You can authenticate ahead of time and then run your commands.  So I kind of merged all that into one big user-friendly thing.  Which part of that thing, that separation of code is because this is the user-facing  stuff, I'm free to add those kind of elements, which is the place for that kind of stuff  in my thinking here.  So not a ton of stuff, but that's just kind of a general bit of code.  So if we look at our process here, right, it's really not much to it.  I've got two parameter sets for my two methods, and if it's one parameter set, I call that  method.  And if it's the other parameter set, I call the other method.  All the API stuff and all my return objects is all handled by the classes.  And that's it.  I could catch the return, and I could do some stuff with it.  But it's already the kind of object I want to output anyway.  So super short, super simple, which has kind of been my theme for all of this, right, keep  everything nice and simple.  I like simple code.  It mostly almost fits on one screen, which is kind of my criteria for writing scripts.  But the thing here is if you want to get complicated, if you want to add more complexity, more  robustness to it, you're not starting with 200 lines of code that you're then trying  to add functionality to.  So I mentioned earlier, you know, if you double the API, is everything likes to return.  Yeah, I love to return error 400 when anything isn't just right.  So in the case of a DNSA record, if you're trying to get a query for a record in a zone  that doesn't exist, you won't get that message, you'll get an error 400.  If you're trying to query for, in my case, if I try to query for too many records and  it's past what the API allows, it doesn't tell me that I've asked for too much, it gives  me an error 400.  So because I've got such a simple script here, what I could potentially do is add more code  ahead of time.  I could maybe check for the zone.  If I define a IB DNS zone class, I can do a get on that zone property ahead of time  and check that it exists.  And if it doesn't exist, I can give much better feedback to the user about that and then maybe  not even query for the A record.  So but this is the place for that sort of stuff.  This is where you could expand upon all that user-facing stuff.  So that's super simple.  Let's see, what did I put next?  I think I put remove next because that was pretty cool.  So yeah, remove.  This is where we get into some of the parameter stuff that I talked about in the beginning.  It makes it super simple, almost too simple.  It kind of feels like cheating.  So if we look at that, and you see I've got my support should process like every good  delete method should probably have a double check.  So here's what we're looking at.  And this line here, this is really the key right here.  It's a mandatory value and I simply say input a record that is a tight DNSA record collection  because I want to allow for that multiple values to come in.  And the main bit of this work is handled by that.  I don't really need to do any parameter validation on that because I know the properties that  are in that object.  I know that it came from an API endpoint unless somebody's really messing with me.  And so that's kind of it.  I've got some extra parameters here because again, this is user facing and I'm trying  to be a kindler, gentler, code developer.  So helpful user-friendliness stuff.  If you did pass in a reference string, say somebody hands you a CSV full of reference  string, I queried these last week and I think they're still good.  Can you go and delete all these?  I'm pretty sure that they're valid.  So I'm like, all right, instead of just saying no, go away and come back with an object,  I'm going to get that for you.  All right.  So you can get any authentication information or if you already have it.  And you give me a reference string.  What I'm going to do is I'm simply going to call the get method with that reference  string.  If it gets an object, I just recall myself with that actual object.  So that's just starting an extra bit.  But that's the idea, right, is in your command lets you do all the user stuff to make things  more user-friendly.  So if I did pass an object, however, I have my should process, a good scripter, and I  simply call the delete method of my class or of my input object has a delete method.  And I pass the parameters and that's it.  That's all I need to do.  So I've got an entire removal command with just over 50 lines of code.  So this would be a good place to put in extra validations and extra checks.  And are you trying to delete a record?  Are you trying to delete an entire zone or did you pass on a list of every record?  So but you're starting from a simple point.  So this is, it's easy to expand upon it instead of starting with this big mess.  So this is one of the nice benefits is everything starts really simple.  I'll do these real quick and just kind of cover.  So set is a similar and kind of layout to delete or to remove, right?  I'm going to expect an object or this or collection of this particular type of object.  That handles all my validation.  If you give me a reference string, I'll take that and I'll go and query for you to be nice.  So I'm simply adding here is the parameters that I have chosen to change.  So interesting thing here.  So what I've chosen to do and I don't know if this is the best answer for this sort of  thing, but it seems to work pretty well.  I set these to default values that are unlikely.  And then what I have set in code is if these values are changed from those unlikely default  values, that tells me you want to modify that property.  I started with like a PS bound parameters.  The trick was though is first, again, this is a design choice, but so PS bound parameters,  if I don't specify comment, it'll be no.  It won't be in there, but what if I wanted to set comment to no?  I can't pass no to comment because it'll just be no.  So I'd have to add a whole set of parameters and say maybe a switch.  Do I want to change the comment?  Yes.  Okay, well you want the comment to be.  And then I've got 20 parameters instead of 10.  So again, the design choice, this is the option I went with because I need to separate between  a value, no, and unset.  I've got kind of three states that I wanted to deal with.  All right.  So that was how I chose to do that.  But again, the, where's my process block?  Oh, there it is.  That's weird.  Okay, so the way this looks, same kind of thing.  If you give me a reference string, I'll go do a query and get that for you and be nice.  If you have changed any of these values, so this is where I check for that change from  the unlikely value.  If you have changed any of these values, I'm going to call a set.  Now I chose to call a single set and this is mostly for simplicity, a single set method  for each value.  You could change the IP address and the comment and the TTL all at once.  You could.  I chose to do them individually.  That's just purely a design choice for simplicity, right?  To make that easy.  I'd say we'll set the one or set the both or whatever.  But again, that's kind of it.  It does that set.  I added the pass through and this is the place for that because remember the set, the way  I wrote the class set does not output anything.  It just does a set and then it goes on about its day.  So I've added the pass through switch so it will return the input object as it's been  modified if that's something you want.  That's the place for that kind of thing, right?  It's in the, in the commandlet.  We're doing on time here.  Should be good.  Let's see.  I think I had to create method which isn't terribly exciting.  Blow that real quick.  So we'll run some of these.  Create method is similar to get.  All right.  You're just passing in optional authentication information.  Name IP address.  All that.  Does that create?  Yeah.  And same thing.  It's you simply pass in the parameters and then it runs the method and there's no, I  didn't write any extra validation but again if you were going to add extra checks, right,  if you're going to create a record into a zone that doesn't exist, you're going to want  to check for that zone first probably.  And do error handling and all that fun stuff.  And this would be the place to do that because it's, it starts really basic which is kind  of, I wanted to lay out this scaffolding of a super simple example and show how basic  this can be and how easy the code can be.  So those are the four commandlets.  Oh and I was going to mention, you know, I chose to, again for simplicity, to do a single  commandlet for a single operation on a single object which is why I have four commandlets  for one class.  You could do, if you had multiple classes, you could do one big complicated commandlet  for all your gets and then do dynamic parameters to decide how your inputs are going to be  and that's a big nightmare.  But again, that's the thing with the code separation is once you've written your classes  and you've defined that mapping to the API, how you call those classes is completely independent  of that.  You can have four commandlets, you could have one commandlet, you could have whatever.  You can reference those from other commandlets, right?  If I were going to call the zone class from my A record commandlet, I can do that because  I've got those sort of elements defined and I'm not, if I change how I want to do this,  I'm not rewriting everything.  So then you get that functionality with helper functions or something like that but the idea  of having things distinctly separated helps for code development.  So let's switch back over to our demo.  Any questions by the way?  I know I'm kind of rattling through this kind of stuff.  Maybe too fast.  If I'm going too fast or a mumbling or whatever, just throw something out of your hauler.  But I've got a lot of stuff to kind of go through.  Oh, I was just to hide that.  I was just to see that.  Okay, commandlets.  So that was my thing.  There we go.  So let's look at what our end result is, right?  We've found our classes and we've done all that work for our endpoint and we've written  our commandlets to be a better interface.  And so we're going to expect commandlets to behave like commandlets, right?  So I'm going to do a query.  Let's see.  Let's see.  Let's see.  Let's see.  It's up a little bit.  Now in this case, I did a query for name server one.  I actually have this on my pre-stage.  It's like a cooking show.  I pulled something out of the oven that's already been cooking.  I've got two records here, right?  I've got two records for server one.  They've got different IP addresses and different comments.  But it's two records.  So if I saved this to a variable, it would just be that array of objects.  Just like any other commandlet when you output, I could pipe that to, actually let's do that,  right?  If I did, pipe it to a table.  You get a table view, right?  So all the stuff you expect from objects, that all works the same.  So let's do an additional query.  Remember, we can query from multiple values simultaneously.  So we're going to get server one, the other one, not both.  So that's that one record.  And that returns.  And again, behind the scenes, this is still that collection of records because of the  method that we chose.  It's got only one element in it.  But the users don't care about that.  I get an output.  You know, I could pipe that to FT just as well, or output CSV or whatever.  And that's cool.  So let's create a new record right quick, just so we can mess around with that.  And that works exactly, again, exactly how you would expect that to.  I got my should process.  So I get kudos for writing good codes.  You should always have one of these, by the way.  If you're writing commandlets that do anything.  And there we go.  It creates a record and outputs it and just looks like everything else.  It's an object of that type.  So that's fine.  Let's call some damage.  Let's do some pipelines.  So I'm going to do my getDNSA record to server one, which remember there are two of them.  I'm going to take each one of those, pass it across the pipeline.  This is like any other commandlet that you would write.  But this is kind of what this looks like.  So I get a should process on the first record.  Not on the whole thing, just on the first record.  And I've got my target, and it gives me the two string representation of the record instead  of just do you want to do a remove on DNSA record?  That would be less useful.  But here I've got the actual object.  So that's much more useful.  For a lot of use cases, I'm going to say yes to that.  And then I get my next one.  Passes across the pipeline, just like you would expect any array pipeline operations  to do.  And that's it.  So that's all kind of standard stuff.  Same with set here.  Let me run this.  Nope.  I don't want to move that down.  There we go.  So if I run this, I get that server two record.  It's got a comment of second record.  Very unique and useful.  If you saw that in production, you'd be very happy that comment was there.  So I'm going to change that comment to something that's arguably not much better.  So same thing.  I'm going to pipe, just like remove, pipe it to DNSA record.  The only thing I have to specify is the comment I want to change it to.  Everything else comes across the pipeline in my piped object.  I get my should process.  And now if I look at my object, I have my updated comment.  So that all works exactly how you would expect commandlets to work with not a lot of extra  work and in my opinion, a much cleaner sort of structured way of putting your code together.  It's readable.  It's understandable.  Anything you could put a script on one screen and not have to scroll through a million.  Of course, I could probably add more comments and furboss logging and all that kind of stuff.  That would make it a little bit bigger, but it starts out very simple.  We'll get on time there.  So any questions about any of that stuff before I flip to the other half?  No, okay.  You're all either asleep or interested.  One of the two.  Okay.  One o'clock.  Yeah, okay.  So that's great.  And you're going to do this and in my case, I've got at this point with my one object,  I've got a single class definition and four commandlets.  If you like me and you like to separate all your scripts out into individual files for  ease of coding is what you see.  You've got something like this.  And then you're going to add to kind of do a rinse and repeat operation.  You're going to go hit the CNAME record next.  You're going to do the same kind of thing with changing properties around for CNAMES  are obviously a little bit different.  And you're going to hit PTR and then you're going to do a fixed address and then you're  going to do, you know, whatever IPAM or DHCP leases.  There's no shortage of objects on your, on a typical API.  So when you're done with that, you're going to have a folder full of stuff.  And again, what we talked about is this is, that's great.  But what we want to do is make that useful for everyone.  So let's talk about now how we're going to go from a folder full of scripts and class  definitions, PS1s to ultimately a module published on the PS Gallery that anyone can download,  import, use, update.  Let's back and do some PowerPoint because that's what we're all here for.  I can do my fat magic, hot keys here.  All right.  No, maybe.  There we go.  Okay.  That's where we left off.  So let's talk about the release pipeline.  That's my son enjoying his own release pipeline.  So release pipeline, there's nothing better than just sitting back and watching a release  pipeline just go.  It's just, anyway, enough of that.  It's not recorded.  You can cut that out.  Okay.  So release pipeline, you hear people talk about it.  Build, test, deploy or source, build, test, deploy.  So we're looking at that.  We want to do that.  And of course we want to automate everything.  So let's look at what that might mean in our case.  So build.  What I think in on build is at the end of a build, let's create something.  Let's create a tangible thing that I can interact with.  So I'm thinking I'm going to make a module.  So I'm going to create my directory structure.  And next one's kind of contentious.  I like to concatenate all my script files into one big PSM1.  I know people like to dot source.  That's cool too.  That's what you want to do.  I don't know if one's better than the other.  I like to argue about that on the internet because it's the internet.  I might make some change or create or make some changes to my module manifest.  If I'm going to go to the gallery, I'm going to need to have all my metadata for that,  versioning information would go in there.  If I'm using external help, if I'm using markdown, I'm going to turn those into that  horrible worse XML, worse than XML mammal thing.  And I'm going to use platypus because I don't want to do that by hand.  So if I'm using inline help, that wouldn't be something I would need to do.  But those are the steps I would define and at the end of my build, however I've chosen  to do it, I have a module and a folder that I could take and copy and give to someone  and they can import and it works just like any module would.  So that's my build.  Deploy is pretty simple, right?  I want to deploy it to the gallery.  And since I've done all my work and build, that's actually relatively trivial.  But between those two things, because again, I want to automate this and I don't want to  deploy a bad change, I'm going to do a test.  So I'm thinking, okay, I'll fire up Pester and do some tests and because I want to do  these in an isolated fashion, thinking, okay, well, everything is local until invoke rest  method.  And invoke rest method is where I leave and I go out to my endpoint and do stuff.  When I'm not going to assume I have an endpoint, it occurred to me that running a bunch of  zone and record deletions and creations on my corporate DNS appliance would probably  be a bad thing.  So I thought I should mock that and save my job.  So that's where I started.  I'll just mock invoke rest method, catch whatever comes from my, when my class calls invoke rest  method, my URI string and my payload, figure out what I need to, if that worked and then  return an appropriate output that my code was expecting and then do sort of an end to  end test.  So the problem with that is what you're really saying there, the short version of what I  just said was I'll just reverse engineer my API.  How hard could that be?  Impossible, it turns out, unless you have access to the source code.  So I actually went down that route and write a bunch of code and I'm parsing URIs and if  it's a get method, I look for this syntax and if it's a post, I'm looking for this syntax  and doing my hash table and all this kind of stuff and I got this big block and I thought  to myself, you know, it's probably the first law of unit testing that if your test code  is more complex than the code you're testing, then it's not good test code and that was  where I was at.  And it turned out it didn't work anyway because once I went actually to an actual appliance,  I guessed wrong on my reverse engineering and it didn't work anyway.  So that's generally a bad idea.  If you want to break this down and do more inputs and outputs before and after invoke  REST method and do true unit tests, you could probably do something like that.  But what I wanted was my end-to-end test because I've got to run this against an endpoint anyway.  So mocking didn't work.  So if you can't beat them, join them.  And I sat in a dark corner and cried for a little while and rocked back and forth and  threw away all my PowerShell books, my T-shirts and then I thought to myself, what does everyone  do when they have a problem they can't solve?  Where did I read in the CIO magazine that all problems are solved?  The cloud will solve all our problems, right?  Microsoft tells us that every year.  So I went to the cloud and it actually worked in this case.  So what that means in this case is what I'm going to do in the cloud is build an endpoint,  a new endpoint, a fresh endpoint, my endpoint for my, in my case, my appliance.  I'm going to build a brand new one while my test against it because it's empty.  I set it up the way I wanted it, it's mine, it's real, it's live and I control all the  variables for that.  So that was kind of the idea.  And there's two things you need when you want to do that.  I'm going to walk through kind of the overview of that here.  You need, because again, we're doing this in a programmatic fashion, we do this automatically.  I'm not going to be manually building anything in the portal.  So we need an ARM template.  I'm also not going to be manually logging into the portal to run said ARM template or  doing any interactive login for PowerShell at all.  So we need some non-interactive authentication information.  So that is kind of the, that's the goal, right?  And we're going to integrate that into our release pipeline and our build test deploy.  So this is kind of what we end up with.  You see I've got our build and our test deploy, right?  These are our kind of our dependent code blocks.  And then I'm going to stick build test environment right before test.  And I'm going to add some extra stuff clean, it's just mousekeeping.  And it is going to be our login.  We're going to do our login there.  Because really, if the login doesn't work, there's no sense in doing any of the other  stuff, right?  If we can't create our tests, there's no point in doing a build.  So I'm going to run all this from AppVare, was my choice.  And so what you end up with with AppVare, and I'll walk through these a little bit.  AppVare uses a YAML file to give it instructions.  You tell it what to do and you tell it what to run and set some variables and all that.  And that just calls AppVare PS1 script.  And then that calls your Saki.  Saki is a DSL that allows you to kind of define dependent code blocks in a nice readable  fashion.  By the way, if this model looks familiar at all, this is all from Warren Frames blog a  few years back on PSDeploy and release pipelines for PowerShell.  I totally stole a lot of his code and then added some stuff to it.  So if you see him, thank him.  If you read that blog, I actually got, at the end, I've got a link to it.  That was a really good way to kind of see an end-to-end process and get an idea of how  a release pipeline should work.  So if you're not already super familiar with release pipelines and all the steps, that  was a great thing.  So I took that and added some stuff to it.  That's what we do here is take someone else's stuff and add onto it.  So that's out there.  You could do the same kind of thing with Jenkins or with Squid or Octopus or whatever, or potentially  locally, right?  Maybe I'm doing local development on a new feature.  Maybe I'm adding the scene record.  And before I go production and maybe accidentally deploy an unfinished thing to the gallery,  I want to run some local tests.  So maybe I'll add the well-named build.ps1.  And that's just going to be a local script that will allow me to call the exact same  production script.  So I'm going to run the same build.  I'm going to run the same tests, which is handy when I'm doing development on a new  feature on this other branch.  I'm not going to accidentally deploy it.  I'll put some checks in there.  But maybe I want to make sure that, okay, if it passes all these tests locally on my  PC, it's still going out to the same endpoint.  There's no reason why it shouldn't work when AppFair runs it.  So that's kind of the idea.  So let's take a look at what that looks like.  And again, this is all, this is the optional part of the presentation because you can really  do this however you want it.  You could add eight steps or two steps or 15 or whatever.  You can branch off and come back and do, sake allows you to do all kinds of really complex.  But again, my theme is simplicity.  So I'm just going to go in a simple sort of fashion through all of this stuff here.  So let me switch over to that.  That is over here.  Oh, wait, no.  So before I do that, that's right.  I was going to talk about Azure a little bit.  And promise this isn't a pitch.  I hit the cloud pitches, but in this case it works.  I'm going to kind of walk you through.  If you're cloud averse like I was, it's handy to see this.  So we need two things.  That's the next thing we're going to do.  I mentioned we need two things when we're dealing with Azure in an automated fashion.  We need an ARM template, and then we need some sort of login information.  So I'm not going to talk about ARM templates because I totally cheated.  I went out to the portal, click, click, click, built my endpoint, which in my case it turned  out to just be a VM with a custom image that this vendor created.  It can be as complicated as your API dictates, right?  It has to be some sort of weird cluster with this web front end.  If you have to define all that, you go out there and you build all that.  I built all that from hand, put it, give it a public IP address so I could access it,  verified that it worked, and then I just did a snapshot.  It's changed since I did it, but they make it super easy.  Actually, let me just show you that here.  They make it super easy to grab that ARM template.  The ARM template that they write.  Oops, just getting in the way here.  There we go.  So this is my test bed for this session.  So in my case, I've just got, oh, this is just a pile of my resources.  I've got my virtual machine, and then of course that has a network adapter and a storage account  and a public IP address.  That's obviously important so I can get to it.  So I just come up, once I get that and I verify, manually verify that that's all working and  accessible from anywhere, I can just come over to my automation script, this tab they  added, and it writes the ARM template for me.  It's not the prettiest code in the world, and you'll probably want to do some cleanup,  but once you get it working, you don't really have to muck with that any ideally.  So that's the world's shortest ARM template demo, but you can dig more into that.  I think there's some sessions on ARM templates that go into a whole lot more detail on that.  What I want to talk about in Azure is the second half, the authentication piece.  That's kind of weird.  So what I'm basically saying is I want to allow an application to authenticate automatically  like a user.  In the old days of AD, we would call that like a service principle.  I want to authenticate as an application.  You kind of have that here.  So what we're going to do, we're going to go to our Azure Active Directory, and you  don't need a complicated, fancy corporate P2 whatever.  See, I'm using my on Microsoft, which is the free one they gave me when I logged in, and  it's a free trial.  Oh, what did I click on?  Oh, I went back.  So I'm going to come in here and what they call app registrations.  I'm going to create a new web app to do my login.  This gets in all kinds of weird OAuth and application.  We're not going to really do any of that.  We're just creating an application so we have a security context on which to authenticate.  Let's come in here, a new application registration.  Again, what we feel in here matters very little, and you want to give it something that you  can find later.  I've done this a few times.  We'll call this InfoLogstiness Appliance Rest Test, something unique.  Sign-on URL again, because this isn't a real web app that we're authenticating to, or test  call is calling it, but we're not authenticating it.  Sign-on URL doesn't really matter.  You can put whatever.  You can put local host.  I like to put my GitHub repo for the project just to help me remember what I was doing  when I see this later.  I don't even think that's right, but anyway, it doesn't really matter.  It just has to be a URL, because again, we're not going to use that.  What we are going to use, well, we got to go find it.  It's too much to ask for them to give it to us.  So we'll search for it.  Nope, that's not my app, apparently.  Someone else's?  I've done this a few times.  I got some mess.  What did I call it?  This one here.  So what we are going to use is this application ID.  That is going to effectively be our username for our credential object, application ID.  So we're going to copy that off, and just throw that in a notepad or whatever for later  use.  We'll save that.  Now we need a password.  We've got a username.  We need a password.  We're going to go to settings, go to keys.  That's the GUI part's almost over.  So we're going to create a new password object.  Just call it whatever.  We'll call it password, because we're super creative.  Duration of how long you want it to last.  We'll just say never.  And then value will be displayed on save.  So we're not going to specify the password.  We're going to let Azure pick it.  So we're going to save this, and it's going to give us this password.  Notice it says you won't be able to retrieve this later, so you'll want to memorize that  immediately.  Or I guess we could, for demo sake, we'll just copy it out.  But I would totally memorize that if I wasn't doing a presentation.  So you're going to save that off.  I'm going to memorize it.  So now we've got our username and password.  And we can stick that out and out there.  We can stick that in wherever.  Of course, it's a username and password, so we're going to talk about how to obfuscate  that.  We don't want to put that in our source control.  That's on GitHub, or Azure login on GitHub is probably a bad idea.  So that's enough of the fancy web interface.  And that's great.  But anything, just about anything you can do through the Azure portal, you can do through  PowerShell.  So let's do it through PowerShell because that's way more fun.  So I've already done the interactive login here.  I think unless it timed out, I mean we'll find out.  So when you do these, we're going to do the same thing.  We're just going to create an application registration username and password.  In this case, rather than have the portal create password for us, we're going to specify  it.  So we're going to make a nice long password with a special character and uppercase, create  a secure string from it.  And we're going to call this new Azure RMED application.  And same kind of stuff that we filled in, right?  We give it a display name.  We give it a URL.  And in this case, we give it the password rather than asking us to specify it.  But we're going to run that.  Let's see if that's still logged in.  Oh, that's right.  I keep doing this over and over again.  Let's change this a little bit.  Again, the name doesn't matter.  Oh, why not?  I didn't clean that up.  I got to change this, too.  It's a problem with demos.  They make a lot of garbage.  There we go.  So now we've got an application.  Let me look at it.  And it's just an object with some stuff in it.  So that is the PowerShell version of all the pointy clicky stuff that we did.  And this is better.  But they match to the same thing.  You've now got an application ID.  And it's got a password.  So you've got the makings of a credential object.  So let's go through and let's do the next part, which would be to how you would actually  use that to log into Azure.  So we could use the information that we memorized.  I mean, wrote down.  But we're not.  We've already got it in PowerShell here.  So we're just going to use that.  So we're going to create your standard PS credential object.  No big deal.  We're using the application ID, not GUID, and the secure passwords.  Our application ID is our username.  So we're going to create that.  And when you do this here, you have to get the tenant ID.  And there's a couple different ways of getting that.  You can write it down.  I heard this actually a website called, like, what is my tenant ID?  And you give it a URL and it'll tell you, this is your tenant ID.  Or this is their tenant ID or whoever.  So that's kind of cool.  We're going to use this Git Azure RM subscription.  And that's just a way of getting that.  We just need that to tell it where we're logging into.  And then we're going to use log in Azure RM account.  We're giving that credential object.  And the tenant ID, we're telling it this is a service principle log in so it doesn't  try to do any interactive nonsense.  And that is just a trivial thing.  It runs that.  And then it logs in.  It spits us back our information saying we're logged in.  So this is the bit of code that we put in our build pipeline.  We obfuscate our password, obviously.  But this is the code.  This is completely non-interactive.  So I use the appvair encryption.  Yeah.  So you're just saying, yeah, I just go in and if we have time, I'll show you that.  But yeah, that's super simple.  So yeah, I just use that.  When I'm doing the local build script, it's not appvair.  I just build all those passwords ahead of time and then pipe them in through parameters.  Yeah.  Let's see.  Where was I on that?  Yeah.  So I'm going to walk through what this kind of looks like just to kind of give you a look  at how that looks.  So I'll start with appvair.  So this is the appvair.yaml file.  If you haven't seen this, let me expand all my stuff here.  Yeah.  Well, I'm going to expand it all.  So the yaml file is just a basic instruction set for appvair to kick off.  When you create an appvair account, you tie it to your GitHub account.  And so I'm giving you a couple things here.  I'm telling it, if I have a commit message that says, update it, read me.  Don't do a build.  I don't need you to do anything.  Just go back to sleep.  I'm only doing a build on the master branch so I can do commits to any other branch without  worrying about it kicking off a deploy.  And then the rest of that mostly is environment variables.  That's kind of its method of passing information along to the build server because this is  all running on their build server, which is just a PowerShell session out on their server.  So I've got my module name and my resource group, and that's used when I'm developing,  you know, spinning up my Azure environment.  I've got an admin password, which of course I want to obfuscate.  So this is what that looks like.  You get this secure string.  And appvair provides that.  It crypts it with your account so it can always decrypt it.  And it does a pretty cool thing when you use this secure password string method.  When it runs its build system and if it uses that password in any sort of visible way,  it keeps track of that and it'll obfuscate so you get a bunch of stars on the output  screen unless you do like write out whose password or something stupid.  But it'll protect its, it keeps itself from screwing up and then, you know, lets you kind  of do your own thing.  But it takes a little effort.  So, you know, I've got all kinds of stuff encrypted.  I encrypted my tenant ID just for fun and my app login.  That's one I already made, obviously.  My PS Gallery key, of course, is important.  You don't want anyone deploying as you.  So we just add all those in there.  But yeah, you have unencrypted variables, encrypted variables, they're treated the same  way.  $e and v colon, these all show up as environment variables when you're running your scripts.  You just treat them like environment variables.  And then I'm telling you to just run this test script, right?  You can do a build script and a test script and you can let appvair handle those stages.  I'm just calling one script and letting Socky handle all my ordering of my steps.  You can do that kind of whatever you want.  So that's going to call appvair PS1.  All I'm kind of really doing here is setting some variables to other variables.  The reason I'm doing that is I don't want to be everything to be, I don't want my Socky  script to be tied directly to appvair, right?  Because again, I might use Jenkins or something or my local build script.  So this is kind of a shim script because I don't want to reference appvair build folder  in Socky because I can't use appvair build folder if I'm not using appvair.  So I have project root and artifact root and module version.  I import some modules that Socky might need to run.  Obviously I need Socky installed to run Socky in Pester and Platypus.  So I just install some modules because again, at the end of the day, a build system is just  a PowerShell environment spun up that you're running scripts against.  That's where they all build system is.  So I'm going to call invokeSocky and give it my script.  It's going to walk through that.  This is what appvair is running.  And the way this is laid out, oh, that didn't minimize.  That was going to do a whole big, pretty thing here.  Let's see.  I didn't do it.  I don't know.  I'm second.  I got this new extension called fold.  It's really cool for auto folding stuff except when it isn't, apparently.  So and I wanted to kind of have this view because I want to show the layout.  So domain specific language uses this concept of a task, right?  So they do a sort of an inverse thing.  Default always runs.  The default depends on deploy and clean test environment, which is my last one.  Of course, deploy depends upon test.  So it's going to run test first.  And test depends on build, et cetera.  So it runs in a top-down fashion from a bottom-up dependency.  Kind of weird.  But that's kind of the idea of Socky.  And you can do branches and you can do all kinds of extra dependencies.  I didn't need anything too complex.  And for a basic PowerShell module function, you don't need anything too complex.  So let's see.  So I mentioned I do my login there.  Oh, no.  I'm going to use my hotkey here.  So I check if my build system is AppVayer.  In my case, I'm doing AppVayer and local.  So if I'm doing a local build, I'm just going to use an interactive login.  So if my build system is AppVayer, I'm going to do my non-interactive login that we just  saw with our credentials.  And notice I'm referencing Azure App login ID as an environment variable.  That was encrypted.  But throughout there, I don't need to do anything special to tell it to decrypted.  It just handles that.  Same with my password.  It just handles that.  And then this is what we just saw.  We log in with a service principal log in.  I do that first because I don't want to bother doing anything else if that fails.  It's all going to go south from there.  Clean is just some, when I'm running iterations with a local build, you know, you got to delete  some folders.  So let's look at build.  Build is, that's what I was talking about with, I want to build a module.  So this is all my code to turn a pile of PS1s into a usable module.  A couple things in here.  One that's really been useful is the versioning.  So AppVayer will give you a build number.  And that's an exposed variable environment underscore build, build numbers and all like  that.  And that will increment every time a build runs.  So what I'm going to do is I'm going to use that build number in my module manifest as  the version number.  Just automatically increment.  So that way every time I do a deploy to the gallery, it goes up by one.  If I want to change my major, minor version manually, if I'm, oh, this is so much better  than the last one, I'll make it 1.2.  You know, I can do that.  But this helps me not worry about unincreasing or having, you know, versions step on versions.  That's all just automatic.  So that pretty works.  If I ever get to whatever they, whenever that number rolls, I don't know what's going to  happen.  I don't know if that rolls at 99 or 999.  I don't know.  But I'm only like 50.  So that should last me for a pretty good while.  So that's one of the things I'm going to do with my update module manifest is I'm going  to take, I've got a static module manifest that has everything you need to run that module.  It's pretty much, most of that information is static, right?  I update the copyright because it kind of feel like I should.  I don't know.  And then I have some variables there.  The module version is really the only thing I'm changing here is I'm going to increment  that based on my build number.  So when I deploy it again, it's going to show up as new.  Which PowerShell Gallery likes that sort of thing and PSGit likes that sort of thing.  And this is where I'm going to do my platypus, right?  So I create my new external help.  If you're doing, you know, external help or markdown help, that's important.  So that's all kind of done.  And when that's, if that works, it's okay, looks at errors.  If any of these bomb out, throw an error, the whole thing will stop.  The dependency sort of depends upon build, but really depends upon build not failing.  That's what it's actually saying in the short end.  So next I'm going to build my test environment.  And that is my Azure deploy, basically.  So I've already done my login, which presumably if I've gotten at this point that passed.  So build test environment is two things, really.  I'm creating a new resource group based on an environment variable I set with a location  that I set there.  And then I run through, there's this command, it doesn't do a whole lot, test azure rm resource  group deployment.  Those names, jeez.  That's going to do some basic validation on your ARM template.  Give you a reasonable chance that it might work.  So errors still get through, but that's something worth running.  So again, I'm checking that that passes.  And then I'm going to run my deployment.  Now I have this ARM template that I downloaded that I let the portal write, which is just  a big steam and mess.  Everything's a parameter, it's like 800 parameters, and they're all static values anyway.  It's ugly, but it works.  So you can clean that up if you want or not.  Just close your eyes and pretend it's not too bad.  So I'm going to pass the resource group, obviously, to put it in.  There's my template file, my azure deploy JSON.  I just save that in my source control there.  There are any numbers of parameters I could pass to it.  The only thing that I want to pass to it, really, because nothing really changes, but  I do want to pass my password to it.  Because again, I don't want to put a password in my ARM template any more than I want to  put a password in the rest of my code.  So I'm going to pass that password, and I'm going to let AppVare decrypt that admin password.  And that's going to happen all kind of behind the scenes.  So that's going to deploy my azure environment, and that takes about eight minutes.  So that's not bad.  It's going to look for that status, that provisioning state.  So if that's succeeded, it's then going to continue on.  If that fails, of course, it's going to stop because there's no reason to run tests against  an environment that doesn't exist.  My tests are just your standard Pester tests.  And so that's just going to gather some data and run invoke Pester.  And the way I've organized the tests, I won't go into that because we don't have three hours.  I wrote a lot of tests.  It's kind of embarrassing.  But the idea there, because I now have a full environment, I can do full tests.  I don't need to test this piece or this piece.  I can just say, create a new DNSA record with the name of server one.  Get the DNSA record.  Is the name server one?  Okay.  Good.  Is the IP address what I said it should be?  Good.  Okay.  Now take server one and delete it.  Did it delete?  Yes.  So I test the creation, and then I can test the deletion.  In between that, I can test all the querying.  There are two server ones with an IP address of whatever.  And again, I don't have to do anything really complicated because I just have an environment  to run against.  I'm not really doing weird unit tests.  I just run the full thing.  Okay.  And so that's that.  And then deploy is really, is pretty straightforward.  We've actually made it this far.  Publish module.  That's really it.  Right?  I've got my module name.  It's in my environment.  And then my API key is my encrypted string that I let out there and crypt for me.  That's really how it is.  And then because I'm a cheapskate, I added clean test environment.  And that's going to go back to Azure like two minutes later after my tests are run and  delete everything.  Well, delete everything in that resource group, not everything else.  And that's really cool.  Not to get all weird and evangelize-y, but basically what I've done there is created  an environment automatically, run 400 tests or some un-gully amount of tests against it  in like three minutes, and then delete it.  And so what I pay for is that three minutes of runtime, which they usually round down to  nothing, which is nice.  Yeah.  So that's kind of that.  How are we doing on time?  I started this late.  When is this in?  12.45?  Okay.  So yeah, we're about on track.  So any questions on any of that stuff?  Okay.  Now, a lot of this is up to you how you want to do it.  And it's very much up to design choices.  I kind of wanted to point out, you know, kind of walk through and end-to-end things.  Just seeing that from start to finish is, for me, it was really helpful to see that  I didn't really get the individual pieces until I kind of saw how it all walked through.  So let me show you.  Sorry.  Yes?  Can we switch to the AppVayer?  Yes.  I was actually going to show you that next.  Yeah.  We got a few minutes.  So let's see AppVayer.  Now, that was super simple.  It's almost disappointingly so.  So I'm going to AppVayer.  I've logged in.  You can link it with a number of different accounts.  I've linked it to my GitHub account.  That seemed to make it sense.  So this is me logged in to AppVayer.  And I'm looking at, in this case, I'm looking at my latest build.  Let me show you this here first here.  So this is a history of all the builds that have been run.  And you see over here it's got, it has the build number.  So it's pulled in the 1.1 and the 1.2 is for me.  I put that in my module manifest.  Actually, I think I put it in a variable first.  It came for me somewhere.  And I had that.  And so I control the incrementing of those codes.  But then you see AppVayer is doing 60, 61, 62, 63.  So I'm just grabbing that.  It's handling that incrementation.  The message here is just whatever my commit message happened to be for that master branch.  So you can see here I added an IB lease object to handle DHCP leases and define them as a  class so then I incremented that.  So if we look at one of those here, we'll look at this latest one here.  Again, because all a build system is, it's just someone else's PowerShell session running  your scripts for you.  And so what it does, the first thing it does is it, any build system will do this.  It uses get to clone the repository.  So it gets a fresh copy of all the data in a local folder.  And then it has its YAML that calls the PS1.  The output of this is really all up to you.  This is probably one of those times when it's OK to use write host because you're not outputting  to anything but the screen here.  You're not going to pipe to anything.  So I have these lines, right?  It just outputs lines to separate the sections.  Ideally, you'd put in some good information about where it was in the process.  I didn't do any of that.  So there's not a whole lot in here.  So but in it, I would do a login and I would probably say logging into Azure.  Login has succeeded.  But this is, Sake is doing this.  So Sake is telling you executing the init task that is defined in that block.  And clean executing build.  So there's some output I just didn't catch again.  This is just sort of, go away a little.  So this is again, if you're running Sake locally, this is what you would see.  Just like on the build system, this is what you see.  It's just a PowerShell session.  So you can see not a whole lot of output when I run my tests.  Here's where you see all my Pester tests.  So I guess I could talk about these real quick.  So I've broken these up into sections, right?  So for each object I've created, like in this case, I've got this web session test  and this IB view.  This is an infobox construct.  But I've got networks and DNS zones and DNS records.  So you can see I'm doing, I've got this, the way I did it to save myself from typing is  I do a, in order to do a remove test, I need an object.  And in order to do a get test, I need an object.  So I'm going to run all my create tests first.  I'm going to do two things with that.  I'm going to create all the objects so that I can use them later.  And I'm actually going to test that they were created correctly.  So I do my create tests for each of my records.  And then I'm going to run my queries, query against all the different kinds of permutations  of how to find stuff.  And then at the end I'm going to run all my deletes.  And I didn't write really fancy Pester tests.  So I just wrote a lot of them because that should be the same thing, right?  So and then I wrote some more and then I got tired and then I wrote some more and then I  deleted some stuff and I wrote some more and then my test complete.  And then at the end, this is all soccer here.  Soccer gives you this little report.  And so it'll tell you how long each section took.  So you can see it, the build test environment, about eight minutes to deploy to Azure.  My tests took about just under four minutes to run.  And then I did my clean.  Deploy takes no time at all and then clean.  So that's soccer handling most of that output.  And I've various just sort of running that on your behalf.  Jenkins and all those other ones, they probably work in a similar sort of fashion.  Let me show you while we're in here.  I think we've got a few minutes.  The encryption thing is so you go to your account because it's based upon your account  and you have all your stuff for your standard account stuff.  They'll let you give them money if you want to.  But you don't have to.  This is all free.  I should mention that.  The app there thing for basic setup is completely free.  So they have this section here, encrypt data.  So you're going to hear, and this is just encrypting it for your account.  So for any project that you have, this works.  You just put in whatever, a nice secure password that no one would ever use, encrypt it, and  it spits out that encrypted value.  And it also shows you the syntax for your YAML.  So you just copy and paste that, stick that straight in your YAML, probably change the  name of the variable.  You wouldn't want to have more than one of those.  But yeah, so that's really all there is.  App there makes that super simple.  If you're going to run a local build system, obviously you wouldn't use those.  That's going to be unique to App there.  But that's where that shim script comes in, right?  You're going to pull those in and then set the values to something that you expect to  be more modular.  Let's see.  That, I believe, is, well, maybe.  Okay, come on.  Yeah, so I like to do all that again.  Okay.  That's, I guess, the review.  That is what we talked about.  I'm going to walk through the build script.  It's basically the same kind of thing as the App there script.  It's just setting some variables and taking more interactive input.  And that's it.  That's what I have.  I am now exhausted and passed out on the couch.  I've got a couple of links up here.  The top one is the link to this presentation.  That repository has this slide deck.  It has the demo code and all that kind of stuff.  The next one is the actual project that I based this on.  So if you actually have an Infoblox and you care, check that one out.  It's on the, it's actually on the gallery as, I think I called it Infoblox Command.  There's a few of them out there.  But if you want to check out the larger set of code, Infoblox Classy is where that's  at.  That last link is Warren's blog on the PS deploy.  It's a little old.  I mentioned to him yesterday, I was like, hey, I loved your blog.  I'm going to actually use that website and come in.  So, I don't know, take it for what you will, but he had some links in there to some other  work and kind of how this release pipeline lays out.  And it was really helpful for me to kind of understand and I based a lot of what I did  off of that.  So I want to include that link.  So that's it.  Any questions?  We're finishing a little early, but yes.  I have a little novice question.  What did that Sel and the class come up with?  That's the problem with something like this is because the classes have methods, APIs  have method, invoke rest method has methods, so it's hard to talk about because it gets  muddled real quick.  But anyway, so that's it.  That's all I have, I guess.  We can be the first ones in the lunch line, so that's cool.  Thank you all very much.  Please.  Thank you.
Building_cmdlets_0_47.png Building_cmdlets_48_95.png Building_cmdlets_96_143.png Building_cmdlets_144_251.png Building_cmdlets_252_3131.png Building_cmdlets_3132_4439.png Building_cmdlets_4440_13199.png Building_cmdlets_13200_13799.png Building_cmdlets_13800_14039.png Building_cmdlets_14040_14087.png Building_cmdlets_14088_14135.png Building_cmdlets_14136_14411.png Building_cmdlets_14412_16727.png Building_cmdlets_16728_16859.png Building_cmdlets_16860_17903.png Building_cmdlets_17904_25799.png Building_cmdlets_25800_25979.png Building_cmdlets_25980_29711.png Building_cmdlets_29712_29771.png Building_cmdlets_29772_32255.png Building_cmdlets_32256_33263.png Building_cmdlets_33264_37259.png Building_cmdlets_37260_37415.png Building_cmdlets_37416_41387.png Building_cmdlets_41388_44375.png Building_cmdlets_44376_45647.png Building_cmdlets_45648_46199.png Building_cmdlets_46200_46679.png Building_cmdlets_46680_47375.png Building_cmdlets_47376_49811.png Building_cmdlets_49812_55631.png Building_cmdlets_55632_57011.png Building_cmdlets_57012_58715.png Building_cmdlets_58716_58859.png Building_cmdlets_58860_60359.png Building_cmdlets_60360_62567.png Building_cmdlets_62568_63851.png Building_cmdlets_63852_64427.png Building_cmdlets_64428_64835.png Building_cmdlets_64836_65027.png Building_cmdlets_65028_65387.png Building_cmdlets_65388_67727.png Building_cmdlets_67728_68063.png Building_cmdlets_68064_69587.png Building_cmdlets_69588_72323.png Building_cmdlets_72324_74507.png Building_cmdlets_74508_74963.png Building_cmdlets_74964_76175.png Building_cmdlets_76176_76283.png Building_cmdlets_76284_78587.png Building_cmdlets_78588_78635.png Building_cmdlets_78636_78779.png Building_cmdlets_78780_78827.png Building_cmdlets_78828_79067.png Building_cmdlets_79068_79859.png Building_cmdlets_79860_80747.png Building_cmdlets_80748_80903.png Building_cmdlets_80904_80963.png Building_cmdlets_80964_81035.png Building_cmdlets_81036_81095.png Building_cmdlets_81096_81179.png Building_cmdlets_81180_81971.png Building_cmdlets_81972_83483.png Building_cmdlets_83484_83783.png Building_cmdlets_83784_84035.png Building_cmdlets_84036_84143.png Building_cmdlets_84144_89075.png Building_cmdlets_89076_91907.png Building_cmdlets_91908_91967.png Building_cmdlets_91968_92027.png Building_cmdlets_92028_92087.png Building_cmdlets_92088_92183.png Building_cmdlets_92184_93011.png Building_cmdlets_93012_98387.png Building_cmdlets_98388_100235.png Building_cmdlets_100236_100955.png Building_cmdlets_100956_104543.png Building_cmdlets_104544_104651.png Building_cmdlets_104652_106379.png Building_cmdlets_106380_106487.png Building_cmdlets_106488_108443.png Building_cmdlets_108444_108719.png Building_cmdlets_108720_108815.png Building_cmdlets_108816_109343.png Building_cmdlets_109344_110471.png Building_cmdlets_110472_110627.png Building_cmdlets_110628_110963.png Building_cmdlets_110964_111299.png Building_cmdlets_111300_111419.png Building_cmdlets_111420_111515.png Building_cmdlets_111516_111575.png Building_cmdlets_111576_112115.png Building_cmdlets_112116_112259.png Building_cmdlets_112260_112499.png Building_cmdlets_112500_113135.png Building_cmdlets_113136_116519.png Building_cmdlets_116520_117971.png Building_cmdlets_117972_118139.png Building_cmdlets_118140_121451.png Building_cmdlets_121452_122771.png Building_cmdlets_122772_123179.png Building_cmdlets_123180_125711.png Building_cmdlets_125712_127367.png Building_cmdlets_127368_130235.png Building_cmdlets_130236_130331.png Building_cmdlets_130332_131447.png Building_cmdlets_131448_135203.png Building_cmdlets_135204_135275.png Building_cmdlets_135276_135383.png Building_cmdlets_135384_135443.png Building_cmdlets_135444_135815.png Building_cmdlets_135816_136871.png Building_cmdlets_136872_137351.png Building_cmdlets_137352_138323.png Building_cmdlets_138324_138431.png Building_cmdlets_138432_138539.png Building_cmdlets_138540_138791.png Building_cmdlets_138792_141851.png Building_cmdlets_141852_142907.png Building_cmdlets_142908_143099.png Building_cmdlets_143100_143171.png Building_cmdlets_143172_143495.png Building_cmdlets_143496_146315.png Building_cmdlets_146316_146639.png Building_cmdlets_146640_146687.png Building_cmdlets_146688_146783.png Building_cmdlets_146784_146829.png
Morning everybody, my name is Jeremy Murrah and I'm here to talk about, I need a deep breath to say all that, building commandlets for REST API with PowerShell classes, Azure based integration tests and continuous deployment. They gave me a double link session so I'd have time to read all of that. And the reason there's so much up there is rather than do a deep dive on a single topic like Kirk's debugging session or some of the other ones, I wanted to kind of cover an end to end scenario, like a project you might do, where say you start with this super important REST API that you need to wrangle, that's not swagger based because you can just let that write its own code, but say you've got this API, they didn't write their own PowerShell commandlets for whatever reason, they think it's a fad and we're all going to switch back to Perl or something. But you've got this API and you're going to go through the process of turning that into something that's a lot more user friendly, but you're going to keep going. And ideally, ultimately end up with something that you publish out on the PowerShell gallery so all the rest of us can use it and then I'll have to go through this process. So I kind of wanted to touch on all the pieces of that that you might see. So we're going to go through all of that. This was the specifics for my demo code and my project was something I did with, it's a device called Infoblox, that's the company name, I forget the appliance. If anybody is familiar, it's a DNS, DHCP, IPAM kind of appliance. So yeah, we have that, they have an API, they're sticking to their Perl API, they're sticking to their guns. So they have a REST API and all that. So I kind of went through a process of writing commandlets for that and just kind of kept going and it's out on the gallery and that was super useful. So some of the stuff is specific to that but I'm going to try to separate out the specifics of that implementation and point out what's generic to this kind of structure as a concept that you can reuse for something else. Anyway, so that's the idea. So let's do that. So what a REST API is, well it's a lot of things but at its real basic core, it's a URL representation of an object or multiple objects and then you've got your standard methods that you can perform against it, right? Your HTTP calls, your git, your post, your delete, you know, pretty basic stuff there and there's some extra, some functions and extra, but that's the general idea. Classes are a similar kind of thing. You've got a representation of an object with defined properties and then these static methods. And looking at this, there was, seemed to be, you know, a fair bit of carryover, kind of a one-to-one mapping. So that's kind of the genesis of where this idea came from. That and also PowerShell 5 had just come out and I was looking for an excuse to play with classes because why not, right? So this kind of started in there. But why classes? You can do this with helper functions or you can just do this, you've always been able to do this with standard PowerShell. We're just doing invoke REST method after all. Well, so like I mentioned, part of why I started was really just to kind of play with it, right? And try something new that hadn't been done before as far as I knew. What I found was a couple of really interesting sort of benefits to it when I started playing with it. What, you know, we talk about in PowerShell, the thing is always have a function that does one thing, right? And that's for readability and that's for ease of use, ease of coding later when you're trying to figure out what you did. And so that's a common model and that's really helpful and when I started looking at doing with the REST API and putting a commandlet in a way, even unlike a get, you know, get some data of this particular type, you're kind of doing two things. You can think about it. You've got your invoke REST method and you've got to build that URI string or that payload and do all this work to parse that data and then send it off and then get your return. Hopefully instead of a 400 you get an actual return and then you've got to do something to maybe parse that output or do some logic around it. So that's all the API interaction code. Then you've got your parameters and your input that you're taking from the users and any output you send them and maybe some extra validation, all that user friendly, gooey, touchy-feely stuff. So in a way those are kind of two separate things. And you can do it all together. It's not terribly complicated but what I found is if you set your class definitions as kind of that API interface. So you're going to make an object and that object is going to be that direct representation of your API object. And all of the work that you do when you are connecting to that API and that representation, that's all going to happen in your class structure. It's a whole separate file or a whole separate bit of code. And that frees you up in your commandlet to focus on all your touchy-feely user stuff, your parameter validation, maybe some extra logic, error handling. APIs love to give you that 400. Any little thing wrong and you get a 400. There's an error buried in there somewhere. Now that I'm an expert on debugging from Kirk's class, I might figure all that out. But it's difficult. So maybe you do some extra logic ahead of time. And because you've separated out some of the API work, and we'll see this in some of the code, it makes it really simple to sort of expand upon that because you've got that separation. So I thought that was pretty interesting. I'm going to start going through this. Another thing that's really cool is kind of what you get for free with PowerShell. You know, PowerShell loves objects. We all know that. And when you do objects in and objects out, you get a lot of stuff for free. Right? You get all that pipelining and you get stuff like FormatTable and FormatList. That all comes for free. Well, PowerShell really likes typed objects, objects of certain type. When you can tell it just something other than a PS object, you get even more for free. So on the left is something you might do. Let's say you have a, you've got a commandlet that you're going to do a set. So you've already written the commandlet that retrieves some data from your API or from some other source. You want to, because this is PowerShell, you want to pipe that directly over to your set command. And what you want to do without any, you know, too much fuss, but if you're passing in a PS object, you want to have a, make some assumptions about that input, right? You need to know that, okay, I'm expecting that this PS object is coming from my get command, but I can't guarantee it, right? A PS object can come from anywhere. You can craft it by hand or it can come from import CSV. You don't really know. So maybe I'm going to use the value from pipeline by property name and I'll say, okay, I expect that my input object should have a name property and maybe I've got a numerical value and a string value. So I'm going to define all these because I'm going to, I'm going to need that data anyway, but also I want to make sure that my object is the right kind of object. All right, so maybe it passes all these checks and I didn't put all this, this would have been a horrible slide, but maybe it passes those checks. But you still want to understand that this is a valid representation of a live object on your API endpoint, right? It may be that kind of PS object, but that could have been something somebody dumped out to a spreadsheet a month ago. And now they're telling you, hey, go clean this up. So you can't assume that it's valid. So you'd probably have to go after all of this and maybe a begin block or, well, I guess it'd be your process block is go out to your endpoint and do a retrieval anyway and validate that, that object that you're going to act upon is really out there and it really has all this data. Especially before you're doing something like a remove. You really want to make sure you're removing the right thing. So you've got to do a lot of work in your command let just to validate your data. Well here on the right, if we have our API object defined from a class structure, we've got a very specific object type. And so we can specify that on our single input object. We take the entire object in, it's of this certain type. It will only pass our parameter validation if it is of that type. And what that tells us or gives us an assumption, we wrote that class definition so we know all the properties. It has to be of that type. We know that or we can have a good enough assumption that it is a valid object on our API endpoint because it's of fewer places that that class can come from. It's going to come from our class definition that did that call and retrieved that object. So this is not a security boundary by any means. Somebody can just make a class themselves and if it was an intent to be malicious, that is still going to happen but that's not the point of parameter validation. This is the save you from causing horrible harm when you're passing in the wrong kind of thing. So you get a lot of that kind of stuff for free because you've defined the object of that type. So you can turn a whole lot of code into one line in a way and that, again, frees you up to do more of the user-friendly GUI stuff. And then, of course, because this is PowerShell and we all like having fun, why not? You never know if something is a fit for the job until you try it. That's my five-year-old doing some experimentation of his own. They said we can't use copyrighted images but I figure I own the copyright on that, both the picture and the subject. So experimentation, that's why we're here, right? So that's enough slides for a little while. Oh, that next is a good one, yeah. So let's switch over and... Oh, wait, I was kidding. There we go. Let's look at some of this here. Let's see. Where am I at? I should have loaded all this up ahead of time. There we go. So I'm going to walk through this here. Let me hide this. Is size okay? You guys see that in the back? Okay. It's horrible up here, these tiny screens killing me. Okay. So this is your basic, what a class might look like. Like I mentioned, I'm doing my use case here as a DNS appliance. So I picked the DNSA record. Everyone's favorite granddad, the DNSA record, to represent with this class. And so you have your... The kind of properties you'd expect on a record, right? A name and an IP address. We get some extra stuff and some of the... Again, this is all the API specific stuff, your properties, and that's going to be defined by your API and hopefully your documentation is really good because that's what makes or breaks an API. So they have defined this object as having these properties and I'm using those here. So we've got a comment is something the InfoLux gives us this ability to put a comment. I've got this... Under score ref is really interesting, that's a unique identifier for every kind of object that the API endpoint gives us. So every A record, every CNAME, every address reservation has a different one of those. And I found out you can actually start a variable with an underscore. So that's pretty interesting. It makes for some funny looking code, especially when it's a property and you've got something dot underscore, but it totally works. That was kind of interesting. So anyway, you've got all your properties that would define whatever the object is going to be. And then you've got your constructors here, which is really just like a new method, right? This is how you, when you are instantiating an object from this class, these are the properties that you need to build the object. And you can do some really complex things here or not. So we've got to just pass in the properties that are defined at the top. Say you must fill in all of these in order to create an object of this type. That just gets interesting, is in the methods, right? Let me just take a sit here. So we talked about, you know, an API has your crud, your create, your read, your update, delete. Those are primarily your four methods of interacting with an API. So we're just going to represent or create a method, a one-to-one method for each of those. All right? So I'm going to walk through these a little bit. I'm trying not to be too tedious with all the code. But so a method, I like to think of a method for me anyway as kind of a little mini function within my class, right? So I've got all these properties here. And these are not, they're similar to the properties that we define in the class, but they're not really related. These are the properties or the inputs that I need in order to execute this method. And that's it. The fact that they are similar to the class makes sense because I'm creating a DNA record. But I could have a method that took completely different inputs depending upon what I want to do. It's a little bit of separation there, but thinking about it like a little function inside your class is kind of how I like to think about it. So a couple of things here. We look at line 14, right? This is the beginning of that. So this is a static method, meaning I don't already have to have an object defined in order to run this method, which makes sense for a create. I'm not going to create an object upon an object that I've already created. I'm going to create an object out of nothing. So we've got a designation here, IBDNSA record. That's the name of my class. So what I'm telling it here is the output from this method is going to be output an object of this type. And that's super important for the parameter validation stuff when we get to the command list, what I'm showing there. It's important. The object output type is really important. You could have, you know, system.object and you wouldn't lose any of the benefits, but that would work. And then the name is whatever you want to call it. It doesn't have to match up with any sort of API verb or anything like that. It's just whatever you want to call it. So the general flow, the body of these, of all the methods are pretty similar. The idea here is you're taking these inputs and doing whatever processing you need to build your payload. And that payload is your URI string, which is that, you know, the URL, the reference to where that object is on your API endpoint. And then optionally, some sort of payload, some sort of body, right, JSON or hash table. You know, that's optional because in case of, and we'll see with like a retrieval or a get or query, you can do all of that on the string. You don't need a payload at all. For something like a put or a post, in this case, it's good to have. So that's, and that's kind of the pattern for all of them is, that's, and this is all the work you would do if you were doing just doing this in PowerShell without classes is you would just go through the logic of building that data set and then you just call invoke request method to submit it, hopefully it passes and you get a return object and then doing some work upon that return object, depending on what that return object is. So, in this case, for, you know, different, on my API endpoint, different object or different methods return different results. So in the case of a create, it doesn't actually return to me back my entire record. It returns back that unique reference identifier that I mentioned. This is this big long string. It's like a GUID without dashes, basically. So it returns that. Now what I need to do, and Snowvers talked about this a couple times, where the return, the way you output things from classes is very different from, from Standard PowerShell, right? Standard PowerShell, you just kind of have to throw it out there to the universe and it'll eventually plinko down and it'll end up out on your screen or in whatever your output is. You don't do those classes. You have to define one and only one and not less than one output objects. And everything has to return an object, a single return. So you've got to define that and that's, that, that takes some getting used to. But it's nice because you don't accidentally end up with extra output. You know, if you're ever trying to use return and then you have some, something that you didn't catch and it spits it out to the screen, you end up with some garbage output. So you can't do that with classes, but you do have to be aware. So what I'm doing, what I've chosen to do here, and this is just a design choice, what you ultimately need to do because of the output we define is you need to craft an object of this type, IBD NSA record, through some method, craft that object and then that's your return. So what I could have done here is done the colon, colon, new and called the constructor because I've got all the information I need, right? I've got all these properties that came in to my method. I could just build that up. What I've chosen to do instead is call my next method, the get method, just kind of an extra check. So I'm going to send that information that it gave me. In that case, it's that unique reference identifier. I'm going to submit that back to my end point and say, hey, I know you just created this, but can you give it to me again? And when I get that back, the way it's defined, the get method on my API endpoint is defined is to return the entire object. So it's kind of an extra validation for all my properties. If it decided to change the name just slightly or on its end or change my, say my IP address or something, I get all that by just doing that query again. So it's an extra check and it doesn't cost me anything. And then this is also that important part. If I don't get any return back, if it fails or whatever, I have to return something. In this case, I'm just going to return $. But you have to have a return statement for every end point. There's this nice useful error that'll say something, IntelliSense will pop it up and it'll tell you something like all code paths, don't return an output or something like that. So that's super important. So that's kind of a create method. And I'm going to walk through get next because I want to point out how I've used that. And with get, you have actually a couple of different options. This expansion here. So we actually have two get methods we're going to go through because there's two different ways you might do stuff. So this is the get method that I just, I call it from create, right? I've got my unique reference identifier and I'm just going to pass that back to my endpoint. And I've got some extra properties here and I'm going to talk about authentication very briefly because that's very API specific and how you want to handle that. But I've got this, I've got my session which is the web session that I catch from invoke rest method. And that's a nice and easy way of doing authentication. If your API lets you get away with that, then you're in good shape because that's a nice, there are harder ways to do authentication. So I'm passing my session in and I've chosen to, and this is a design choice. It's arguable where you want to put that. But what I've chosen to do is rather than do any authentication in my methods, in my classes, is handle the authentication and creating that session outside of that and then just pass it in. So, you know, six to one half a dozen. It saves me a little typing doing it once ahead of time. And when I get into the command, I'll kind of show you the choices I made there. But anyway, so I'm going to pass in that session is required obviously to have a connection and then there's some information I use to build my URI string that reference identifier. You can see here I don't have a hash table or a body of any type. This is an option with certain methods you can just query. And again, this is up to your API. But I can just do a single URI string to get my return. And in the same way as the create method, I call invoke rest method, get my return object back. But in this case, I'm using that constructor, that new method to craft my object. And I have to do some formatting on the IP address. But using all my return, my return object properties, I just build that object and then I return it or return no. Same kind of rules apply. I've got to return something. So and that's great for when you have that unique reference identifier. But most of the time you don't, right? You're, especially if your API didn't have that. If you are trying to get say every DNS A record for this one server or everyone that has this IP address because maybe I've got some duplicates, right? You're going to need to do a query, which is going to be your common way of interacting with an API. So this would not work at all for that, right? I'm not retrieving, I'm not going to have any of the inputs that I need coming into my method at all to even do a query. So I'm going to make an entirely new method. What they call an overload is what the developers like to call it. So which ultimately is just another method with the same name. And so these are, I'm doing the same thing. I'm doing a get, but I'm doing it in a completely different way with a completely different set of inputs. So rather than try to just have one big one and do if this and that, I'll just make a whole separate one. So it flows more logically because it's a different kind of operation. It starts out similarly. You see this is also a static method because again we don't already have an object, we're getting a new one. Now there's a little thing here with the output. Notice it's the IBD NSA record, but it's that collection notation, that extra set of braces. And that's important because in this case we are returning potentially more than one object, right? Because you do a query, with that unique reference saying you're only getting that one record with this, you're potentially getting more than one. And because of the way classes handle outputs and because of the way we want to be able to do parameter validation when we're wrapping commandlets around this, that's super important to have. It's got some weird little quirks. I'll show you that in the demo, it gets kind of weird, but that's important to have. And again it's a git method. So it's the same kind of thing. And you'll see our properties, we've got a whole lot more coming in, right? We've got our name and our p address and comment and zone and all these are all things that we could potentially be querying upon. And that's defined again by our API documentation. They have said that we can search by all these things. If they didn't allow us to search by something, we'd just leave it out. So there's a little more work going on here, but it's the same kind of thing. We're taking all those inputs and just building up, in this case just building up a very long URL, adding each property. Now the interesting thing with classes and why we have this laid out the way it is is none of these input properties are optional. You have to put something in there. You know, with some of the.NET stuff you can kind of do, get away with like comma, comma, comma, comma, and skip a bunch of stuff. You can't really do that here with PowerShell classes. At least you couldn't when I roll this. So you've got to pass in, if you don't want any of those values, you pass in a dollar or no. So instead of comma, comma, comma, comma, you've got comma, dollar, no, comma, dollar, no, comma, dollar, no. And that's ugly. But since we're wrapping commandments around this, we don't have to worry about that. Users aren't going to see that. But I have to assume that some of these are going to be no. So I just do a bunch of ifs and if the value is in there, I'm going to append it. And that's building the structure of that URL. Yes? You know, I don't remember, I don't know that you could when it first came out. Yeah. That might have changed. But yeah. Now, I could do more overloads if I wanted to make some other assumptions. I could skip that up. But again, since I'm not putting this in front of users, I'm not trying to make it pretty in this case. No, I don't understand. Yeah. It seems like that would be if that's supported, that would be easier. Yeah, potentially. Yeah. Yes? So you have the dollar and all, and you have your type string in full, it doesn't matter if you pass a dollar and all. It's not going to give you the type or math. Not any of the types that I've used. No. Yeah, I don't believe. Now, I think with maybe some of the enums, it might get a little weird. I wasn't able to really do anything with that. So yeah. In most cases, dollar and all can turn into whatever it needs to turn into. Okay. So the same kind of stuff. We're just building our URI string, and we're going to call invokeRest method and catch our return. Oh, one thing I've got in here, which has been proven really useful, is this line 108, this write verbose. So what I'm doing is I'm writing out the URI string that I built before I call it. It's really helpful for debugging. Interesting thing about write verbose, it works, but you can't call, there's no dash verbose switch on a class, right? But we're putting, again, we're putting commandlets in front of this. So when you call your commandlet with dash verbose or set your verbose preference, that carries through to the classes that it calls. So that works, and that's a handy, handy sort of thing. Now our output gets a little different on this one, because again, we are returning potentially multiple objects. Or multiple objects are coming back from our API endpoint. But we still can only do a single return. We can't return this output and then return the next one. You know, we don't get that pipelining operation that we're used to in PowerShell. So we've got to do the, you know, build an empty array and then loop through, create an object for each of our return fields, and we're just doing the same new constructor that we did before. And then concatenating them all together into an array of objects and then doing the return. And because, again, that ties in with our output type, that collection of DNSA records, because it's not a single DNSA record. And it won't let you, you build this array and try to return it as a single DNSA record. That's no good. You don't get to do that. So, but that collection of DNSA records is what sort of represents that array notation. So that's why that's important. So that's those two. Go through here, the set method. So those are our static methods. And those are our two methods of sort of creating an object based upon those API endpoints. Our set method is going to be, obviously, what we're going to use to change a value on an existing record. And because it has to be an existing record, this is not going to be a static method. This is going to be a method that exists on an instantiated object. So it's a little different. I've got, instead of static, I've got this keyword hidden. And that's handy for, well, that does what it says, it hides it a little bit from view. And I'll show you this in the demo. But basically what that does is that hides it from Gitmember and some of the IntelliSense stuff, not all of it. The idea is, because in our case, we are putting commandlets in front of the user. We don't really want the user mucking around with an object.set, an object.delete, right? We want them to use in our commandlets. That's the whole point. So you put a hidden there and it just doesn't show up in Gitmember. You can still force it and you can find it some other ways and you can still call it. But it gives it a little bit of a hidden. And that'd be nice if that were a little more strict in not letting you do that. But anyway, our output type here is void, meaning we're not going to actually output any objects. In our case, we're kind of, and again, this is a design choice. I've decided, the way I'm doing it is the idea of the set being I'm going to make a change and if it works, I've made the change. If it doesn't work, I'm going to throw all kinds of errors or whatever, but I don't necessarily need to return the object. In the commandlets, I add some switches. You can add like the pass-through and say, well, if I say pass-through, then spit me out the object. And if I don't just do it and don't tell me about it, without handling that in the commandlet, because again, that's a user choice sort of thing. So I'm going to do a set and I'm just going to not return any outputs. Same kind of idea, right? I've got properties and these are the inputs that I need in order to make a change to a record. So there's fewer here. Notice there's no name and no, there's no, well, there's no name on this one. Because this is a non, not a static method, I have the record and all the properties on the record that I can just reference with the dollar this keyword. So I can use all of this dot name to reference the name. I don't need to pass it in. The things that I'm passing in are the fields that the API allows me to change. So when I pass in, the IP address is going to be not the dollar this dot IP address. It's the IP address I want to change it to if I choose to change it. And same kind of thing, once I get my inputs is, and this is, you know, it gets kind of repetitive, which is nice when you're writing a lot of this because you can do a lot of copy and pasting. But same kind of thing, I build my URI string, I build my hash table for input and it can be JSON or it can be a hash table, either one. And then I call my invoke rest method. Now here, because I already have an object, and again because I'm not outputting anything, I simply need to change the properties on my existing object to, based upon my return. So in the particular case of my device, that unique immutable reference identifier, it turns out to not be all that immutable, so that can actually change from if you make a significant enough modification. So I am going to, I'm going to update that value. So I reference the dollar this here and you see I changed my reference string. It might not actually change, but I'm going to reference my returns. The return was that reference string, and that's what your API, again, your API defines all this, right? In the case of the get, it returned an entire object back. In the case of the create and the set, actually in the delete, it returns just that reference string. So I can use that reference string and then just make my modifications to my existing record. I don't have to output anything because it's making changes to my instantiated object in memory. So that's that, the delete method I saved for last because it's the simplest out of all of them. Just that you simply reference the URI of the reference string, that's how you reference the URI of an object and just call the delete method of invoke rest method in your delete method to find in your class. And that's really all there is to the delete method. That's pretty simple. Most APIs should behave that way. So that's kind of it for your really, your basic class definition. We're going to add a little flair to that in a second. Any questions on any of that particular stuff? Okay. So that's great. But maybe we want to add a little flair. Part of this was experimentation and all that. So let's look at inheritance. We can maybe save ourselves a little bit of typing. So I mentioned that this unique reference identifier is common among all objects in my API. The delete method, it turns out, is also exactly the same for every object because all I'm doing is referencing that unique reference identifier and calling a delete method. So because those are the same, and I know that they're exactly the same across everything, I can pull those out, put those in a root class, define a class that just has that one property and that one method, and then have all my other classes inherit from that. So my DNSA record can inherit from that root class. My CNAME record can inherit from that root class. I no longer have to then define reference string and delete method, which is, it's like five lines of code, but you get the idea. So take a look at what that looks like. Where'd I put that? That was in here. So I'm going to define a class called IB reference object. You know, of course, the IB is my prefix that I've used to make sure I don't step on anything else, and that's probably a good idea. I've read somewhere that's a good idea to do. So I'm going to define a class that, again, is not going to have much in it. I'm going to have a single property, my reference string, and I'm going to have a single method. And that method is just what we saw. It's exactly what we saw where, just using the underscore ref to an end of delete method. So, and then you've got some constructors. Every class needs constructors. It's a little complicated there. But where that gets cool is, you know, that saves me, that saves me my typing, right? So I can, I can then have a DNSA record that looks like this. So that notation at the top tells it to inherit from IB reference object. And you'll notice I no longer have, in my property list, I no longer have underscore ref, and in my methods, I no longer have delete. And that's great. That saves me a few lines of code. But hey, I've got, I've got this new class, right? It's IB reference class. Let's do some stuff to it to kind of spice this up a little bit. So again, here's a, here's a new reference object with some extra flair in it. We've got our underscore ref property, our hidden method. And if I, well, let's, let's do some extra work. Let's override or let's replace some other method. So we've got this two string method, which you've probably seen on objects all over the place. That comes down, I think, from system.object is where that inherits down, or somewhere up there, you get this two string method. And a lot of times that doesn't work so well on objects. If you've ever tried to out, dump out a big array of objects or a list out to Excel or something else, so CSV, you pull it up, and that column that you expected to have some information just has PS object, PS object, PS object all the way down. Because that property was not a string or whatever, it was itself an object. It's, didn't reference real. So, and that's because what that's doing basically is trying to convert it to a string and not doing a very good job of it. So we're going to replace the two string method. And so instead of getting system.or, in our case, IBDNSA record, we've decided to say that, you know, that, that unique reference identifier is a great textual representation of our object. It's got, and I'll show you that. I don't think I've actually shown you what it looks like, but it's got everything you need to understand what that object is. So in our case, that's a great, that's a better string version of our API. So we're going to add this in here. And because that's in here, every object that we inherit, that inherits from this base class is going to have a better two string property. That's really handy. So, okay, so enough of me rattling off, showing off my laundry here. Let's flip over to my demo and let's run some of these and see what this interaction looks like. Let's see, my stuff might have timed out a little bit. So I'm going to load up. Oh, I did this out of order. I was going to demo as I went. Okay, so we're going to do all the demos at once here. So I'm going to load up our first demo. This is the without inheritance sort of thing. I've got this, this, this web session here. So I'll talk a little bit about this real quick. Again, this is, this is separate from the, the sort of the class discussion, but it's important for APIs. You've got to handle your authentication in some way. And that's very much up to the API and how they, well, I can't get everything, is up to the API and how they implement that. In this case, I consider myself pretty lucky. I'm able to use the credential parameter and the web, or the session variable parameter of invoke rest method. And that's just save that session and you pass, pass a standard credential object. There's some APIs that you have to craft an authentication header and pass that every time. That's really hard. I haven't had to do that yet. So if you do my apologies or my condolences. So but again, that's, that's kind of a separate, how you handle that is kind of a little bit separate from this discussion. The assumption is that you will have done that at some point before you start mucking with objects. Now what I've decided to do, and this is just purely a design choices is take my, my session variable. And likewise this, this grid master, which is, is in my version, API version, which is used for building that URI string. And I've decided to scope those out to script scope, which scoping is really weird. But when you script scope, scope something from within a module that is available to the rest of the commands in that module, but not out to the user. And this demo case, I've script scoped it out to my session so we can take a look at it. So what that looks like is, is just your standard web session variable. If you used invoke web request or invoke rest method, you get this standard session. It's got your credentials and your cookies and all that sort of stuff. And these other ones are just strings. But those are, these are important for, well that one didn't go, but it's just, this is a URL for my, my endpoint, which is a live endpoint out in Azure. So that's kind of nice. And again, that's, I just mentioned that there because I'm, I'm going to be, I've handled that separately so that I can pass that into my classes. I didn't want to make design to show, design choices about authentication within my class. I kind of wanted that to be separate. Except to you how you want to do that. So let's take a look at what these classes look like. Again, this would have been, had I done this in the right order, this would have been before we talked about inheritance. So you see our get methods we've got our two. And this looks similar to what we saw in the definition. I see our first one gets by reference string and it just outputs a single record. And our second one is the query method. And it returns that, that collection object. So that's run through these. Let's do this. And we'll find out if Wi-Fi is working here and we'll get an object back. And I'm going to try to F8 my way out through all these demos so you don't have to watch me type. See how that works. So it's looking at what a record looks like, right? And we're just using the classes here. Notice that, notice that, that thing I've had to type there, right? So I pass in the parameters and on the right order and then a whole bunch of stuff I didn't want to search by. I pass in $1 and all. It's not the prettiest thing, but again, we're going to hide that. So, and I said, just looking at the classes. Let's look at our record here. Let's stretch that up a little bit. So it looks like your standard object is, it is. Nothing fancy there. It returns all our objects. And that's our unique reference string, right? It's got this big, long, unique value, two to the power of a million or whatever it is. It's got our entry name there. So that's what we chose to use as our textual representation of a record. So it's super useful, better than bracket object name. We look at get member. We will see this, kind of what we're expecting to see. You've got your methods and properties. You see the type name is IBDNSA record. Now you can do that, you know, if you had a PS object, you can add type names and all that kind of stuff. But because we defined this natively, relatively natively, we have that there. You'll see we've got all the properties that we had defined on the object. It's nothing new. You've got a two-strain get type, all your standard inherited down methods. We've got our delete method here. If you caught that when I was defining that, I put hidden on set. I didn't put hidden on delete. I wanted to illustrate that. So this is what that hidden keyword does, basically, right? So somebody's mucking around with their output and saying, oh, what do I have here? Oh, delete. That looks like fun. So it would at least hide that. Again, if they did record.delete and they knew what to type, they could still fill that in and cause trouble. But it hides it from the casual. Keeps honest people honest, right? So now that we have a record, let's look at our non-static methods that we can look at and I want to point these out because this is where things get a little weird. So record.set. Well, that's not our set method at all. That's not at all what we defined. That's some garbage. So what that is actually telling us, this is part of that weird thing with it being a set method. That's the set method on a DNSA record collection. What I need to reference is the set method and the delete method on an object. So I'm going to look at the first object in a single item array, our collection, and that is the set method that I expect. I don't know why that is. I'm sure there's someone here who can explain that. This is like some complicated developer stuff. So if you know the answer to this, come see me. I'd love to get more info. But that's something also to be aware of. But again, we're going to hide all that from the user so they don't have to worry about that and then delete is the same kind of thing. So I'm going to fast forward now. This is where we talk about inheritance. We're going to see some of the inheritance benefits in action. So let me load up those here. So let's go get a new record. We're going to do a couple things here. We're going to get another record. We're actually going to get the same record for server one. The same record I got for dollar record. I should have called that record one. That would have made more sense. Anyway, so I'm going to get that record. But again, this is the new class definition that I just created with inheritance. So it's the shiny polished one. And then I'm also going to get the same element using my, that's my new reference object, my base class. And you'll see what I'm passing in is that record two underscore ref. So I'm getting the exact same item from my APA endpoint, but I'm defining it as two different kinds of classes. So you can see what that looks like here. So I'm going to load this up, load that up. So let's look at ref record. And you'll see it only had, if you remember, it only had the one property underscore ref. And that's, so that's all we see. Pipe that to get member. Same kind of thing. I don't have any methods. I fixed that delete, put a hidden back on delete. So you don't see that, but I've got underscore ref there and that's it. My record two, which is the same object on the endpoint is of the DNS record class. So it has all the properties that we expect to see and that's all on how we defined it. If we look at record two, get member. So we've got all that. I fixed that delete, hidden, put the hidden back on the delete. So that's gone from there. And that looks just like record one. Now we added that two string, right? So let's take a look at what that looks like. And for this method, we have to reference the element within the array. So if we look at that, see now we get, or I'll show you in a minute, we get the, that reference string as our output. And that's a much better visual representation of a string object. So I have this here. So I want to point out that that is not a recursive sort of thing or retroactive, I guess. Record two was instantiated after we modified our class definition. A record was, is still that object we instantiated before we added inheritance. So if we look at it, it's two string. It does not benefit from that new and we get the old IBD and SA record. And so that's an important thing is it doesn't, it's not some sort of global all encompassing definition. What you define and when you instantiate that object, it is an object in memory like any other variable and properties and methods are defined on it at the time that you create that class definition or the time that you instantiate it. Okay. And now I'm back in the order that I was initially intending on going through. So let's look at what commandlets will look like wrapped around this, right? We saw this with that bracket zero bracket and the get method. Some of those are not the prettiest user facing things. If you're like me and you're the PowerShell guy in your office, you have a hard enough time getting people to run commandlets, much less getting them to run. No, you just run the class and make sure you put seven nulls and then you put the one and you'll be fine and that's never going to work, right? That's what I worked at. We never worked people. Just kind of go cross-eyed and probably pass out. So let's look at what we can do with commandlets. So we'll walk through these real quick and point out, again, like everything, these get kind of repetitive. But I want to point out a few things in my stuff here. So the get record, our get method we had two, right? So we had two methods of retrieving data. We have get a single record by this reference string and then query by endpoint for anything based upon all these different query parameters. So we're going to do the same thing in our commandlet, but do the parameter method. So we're going to do two parameter sets. One is going to be by ref and then one called by query, which you can call whatever you want, but that's the idea is we're going to make a, because we only want to call one method or the other depending upon what the user is looking for and that's where parameter sets come in. So we've got a parameter set that allows us to pass in our reference string and we got that down here, our underscore ref and then everything else is this by query. So these are all the properties we could potentially query by. I noticed none of those are mandatory because we don't have to query by anything. We run a get without any query strings or any query filters. We just get everything back. You'll see I've gotten in here at the top, grid master and credential and that's part of building that web session. So this is where I've put that rather than put it in the class, I put it in the commandlet. The idea here is, yes. That was a, that's the mode code that IntelliSense would not leave me alone unless I had both of those in there at some point. I think they fixed that, but I just haven't fixed the code. That's all that is. So I got, red curly braces was driving me nuts. So I just kept adding stuff and clicking the light bulb until it shut up and this is what I ended up with. So it works. I don't know what it's doing, but yeah. So what I'm doing here is allowing for a new web session, right? So you pass in your credential, you pass in the grid master that you want to connect to. The way I've set it up, and this is all purely design choice, right? I didn't want to type, I started out typing that every time. Every command I would run, I would add in credential and grid master. Tab complete is your friend. That got to be not good enough. So what I've done is, if a web session, and this is why I script scoped this stuff. So if a web session already exists in the script scope, I will just use it. And that's why these are optional. If there is not a web session or I pass in a grid master and a credential, it will create a new web session in the script scope. So the next command I run, I can leave those off. And that's been super helpful. I'll show you a little bit of that. But again, the authentication stuff is just weird. It's kind of separate from the class stuff. It's important, but you've got to, you know, a lot of it is design choice and how you want to handle that. So again, so we've got our pile of parameters. And again, we're doing, well, our pipelines for our reference string, because you have a pile of those you're trying to query. And so this is my begin block. This is the web session stuff. It's in all of these, but I'm going to only walk through it once. But here's the idea is, if I have a script scoped, or if I don't have a script scoped session, and I do have the grid master and credential parameters, I will create that web session. If I have nothing, then I'm going to bomb out and say you've got to log in first. Anyway, so that was how I chose to handle that. So I only have to type that once. Or, optionally, create that web session before I start doing anything manually with this new ID web session, which is an option, right? You can authenticate ahead of time and then run your commands. So I kind of merged all that into one big user-friendly thing. Which part of that thing, that separation of code is because this is the user-facing stuff, I'm free to add those kind of elements, which is the place for that kind of stuff in my thinking here. So not a ton of stuff, but that's just kind of a general bit of code. So if we look at our process here, right, it's really not much to it. I've got two parameter sets for my two methods, and if it's one parameter set, I call that method. And if it's the other parameter set, I call the other method. All the API stuff and all my return objects is all handled by the classes. And that's it. I could catch the return, and I could do some stuff with it. But it's already the kind of object I want to output anyway. So super short, super simple, which has kind of been my theme for all of this, right, keep everything nice and simple. I like simple code. It mostly almost fits on one screen, which is kind of my criteria for writing scripts. But the thing here is if you want to get complicated, if you want to add more complexity, more robustness to it, you're not starting with 200 lines of code that you're then trying to add functionality to. So I mentioned earlier, you know, if you double the API, is everything likes to return. Yeah, I love to return error 400 when anything isn't just right. So in the case of a DNSA record, if you're trying to get a query for a record in a zone that doesn't exist, you won't get that message, you'll get an error 400. If you're trying to query for, in my case, if I try to query for too many records and it's past what the API allows, it doesn't tell me that I've asked for too much, it gives me an error 400. So because I've got such a simple script here, what I could potentially do is add more code ahead of time. I could maybe check for the zone. If I define a IB DNS zone class, I can do a get on that zone property ahead of time and check that it exists. And if it doesn't exist, I can give much better feedback to the user about that and then maybe not even query for the A record. So but this is the place for that sort of stuff. This is where you could expand upon all that user-facing stuff. So that's super simple. Let's see, what did I put next? I think I put remove next because that was pretty cool. So yeah, remove. This is where we get into some of the parameter stuff that I talked about in the beginning. It makes it super simple, almost too simple. It kind of feels like cheating. So if we look at that, and you see I've got my support should process like every good delete method should probably have a double check. So here's what we're looking at. And this line here, this is really the key right here. It's a mandatory value and I simply say input a record that is a tight DNSA record collection because I want to allow for that multiple values to come in. And the main bit of this work is handled by that. I don't really need to do any parameter validation on that because I know the properties that are in that object. I know that it came from an API endpoint unless somebody's really messing with me. And so that's kind of it. I've got some extra parameters here because again, this is user facing and I'm trying to be a kindler, gentler, code developer. So helpful user-friendliness stuff. If you did pass in a reference string, say somebody hands you a CSV full of reference string, I queried these last week and I think they're still good. Can you go and delete all these? I'm pretty sure that they're valid. So I'm like, all right, instead of just saying no, go away and come back with an object, I'm going to get that for you. All right. So you can get any authentication information or if you already have it. And you give me a reference string. What I'm going to do is I'm simply going to call the get method with that reference string. If it gets an object, I just recall myself with that actual object. So that's just starting an extra bit. But that's the idea, right, is in your command lets you do all the user stuff to make things more user-friendly. So if I did pass an object, however, I have my should process, a good scripter, and I simply call the delete method of my class or of my input object has a delete method. And I pass the parameters and that's it. That's all I need to do. So I've got an entire removal command with just over 50 lines of code. So this would be a good place to put in extra validations and extra checks. And are you trying to delete a record? Are you trying to delete an entire zone or did you pass on a list of every record? So but you're starting from a simple point. So this is, it's easy to expand upon it instead of starting with this big mess. So this is one of the nice benefits is everything starts really simple. I'll do these real quick and just kind of cover. So set is a similar and kind of layout to delete or to remove, right? I'm going to expect an object or this or collection of this particular type of object. That handles all my validation. If you give me a reference string, I'll take that and I'll go and query for you to be nice. So I'm simply adding here is the parameters that I have chosen to change. So interesting thing here. So what I've chosen to do and I don't know if this is the best answer for this sort of thing, but it seems to work pretty well. I set these to default values that are unlikely. And then what I have set in code is if these values are changed from those unlikely default values, that tells me you want to modify that property. I started with like a PS bound parameters. The trick was though is first, again, this is a design choice, but so PS bound parameters, if I don't specify comment, it'll be no. It won't be in there, but what if I wanted to set comment to no? I can't pass no to comment because it'll just be no. So I'd have to add a whole set of parameters and say maybe a switch. Do I want to change the comment? Yes. Okay, well you want the comment to be. And then I've got 20 parameters instead of 10. So again, the design choice, this is the option I went with because I need to separate between a value, no, and unset. I've got kind of three states that I wanted to deal with. All right. So that was how I chose to do that. But again, the, where's my process block? Oh, there it is. That's weird. Okay, so the way this looks, same kind of thing. If you give me a reference string, I'll go do a query and get that for you and be nice. If you have changed any of these values, so this is where I check for that change from the unlikely value. If you have changed any of these values, I'm going to call a set. Now I chose to call a single set and this is mostly for simplicity, a single set method for each value. You could change the IP address and the comment and the TTL all at once. You could. I chose to do them individually. That's just purely a design choice for simplicity, right? To make that easy. I'd say we'll set the one or set the both or whatever. But again, that's kind of it. It does that set. I added the pass through and this is the place for that because remember the set, the way I wrote the class set does not output anything. It just does a set and then it goes on about its day. So I've added the pass through switch so it will return the input object as it's been modified if that's something you want. That's the place for that kind of thing, right? It's in the, in the commandlet. We're doing on time here. Should be good. Let's see. I think I had to create method which isn't terribly exciting. Blow that real quick. So we'll run some of these. Create method is similar to get. All right. You're just passing in optional authentication information. Name IP address. All that. Does that create? Yeah. And same thing. It's you simply pass in the parameters and then it runs the method and there's no, I didn't write any extra validation but again if you were going to add extra checks, right, if you're going to create a record into a zone that doesn't exist, you're going to want to check for that zone first probably. And do error handling and all that fun stuff. And this would be the place to do that because it's, it starts really basic which is kind of, I wanted to lay out this scaffolding of a super simple example and show how basic this can be and how easy the code can be. So those are the four commandlets. Oh and I was going to mention, you know, I chose to, again for simplicity, to do a single commandlet for a single operation on a single object which is why I have four commandlets for one class. You could do, if you had multiple classes, you could do one big complicated commandlet for all your gets and then do dynamic parameters to decide how your inputs are going to be and that's a big nightmare. But again, that's the thing with the code separation is once you've written your classes and you've defined that mapping to the API, how you call those classes is completely independent of that. You can have four commandlets, you could have one commandlet, you could have whatever. You can reference those from other commandlets, right? If I were going to call the zone class from my A record commandlet, I can do that because I've got those sort of elements defined and I'm not, if I change how I want to do this, I'm not rewriting everything. So then you get that functionality with helper functions or something like that but the idea of having things distinctly separated helps for code development. So let's switch back over to our demo. Any questions by the way? I know I'm kind of rattling through this kind of stuff. Maybe too fast. If I'm going too fast or a mumbling or whatever, just throw something out of your hauler. But I've got a lot of stuff to kind of go through. Oh, I was just to hide that. I was just to see that. Okay, commandlets. So that was my thing. There we go. So let's look at what our end result is, right? We've found our classes and we've done all that work for our endpoint and we've written our commandlets to be a better interface. And so we're going to expect commandlets to behave like commandlets, right? So I'm going to do a query. Let's see. Let's see. Let's see. Let's see. It's up a little bit. Now in this case, I did a query for name server one. I actually have this on my pre-stage. It's like a cooking show. I pulled something out of the oven that's already been cooking. I've got two records here, right? I've got two records for server one. They've got different IP addresses and different comments. But it's two records. So if I saved this to a variable, it would just be that array of objects. Just like any other commandlet when you output, I could pipe that to, actually let's do that, right? If I did, pipe it to a table. You get a table view, right? So all the stuff you expect from objects, that all works the same. So let's do an additional query. Remember, we can query from multiple values simultaneously. So we're going to get server one, the other one, not both. So that's that one record. And that returns. And again, behind the scenes, this is still that collection of records because of the method that we chose. It's got only one element in it. But the users don't care about that. I get an output. You know, I could pipe that to FT just as well, or output CSV or whatever. And that's cool. So let's create a new record right quick, just so we can mess around with that. And that works exactly, again, exactly how you would expect that to. I got my should process. So I get kudos for writing good codes. You should always have one of these, by the way. If you're writing commandlets that do anything. And there we go. It creates a record and outputs it and just looks like everything else. It's an object of that type. So that's fine. Let's call some damage. Let's do some pipelines. So I'm going to do my getDNSA record to server one, which remember there are two of them. I'm going to take each one of those, pass it across the pipeline. This is like any other commandlet that you would write. But this is kind of what this looks like. So I get a should process on the first record. Not on the whole thing, just on the first record. And I've got my target, and it gives me the two string representation of the record instead of just do you want to do a remove on DNSA record? That would be less useful. But here I've got the actual object. So that's much more useful. For a lot of use cases, I'm going to say yes to that. And then I get my next one. Passes across the pipeline, just like you would expect any array pipeline operations to do. And that's it. So that's all kind of standard stuff. Same with set here. Let me run this. Nope. I don't want to move that down. There we go. So if I run this, I get that server two record. It's got a comment of second record. Very unique and useful. If you saw that in production, you'd be very happy that comment was there. So I'm going to change that comment to something that's arguably not much better. So same thing. I'm going to pipe, just like remove, pipe it to DNSA record. The only thing I have to specify is the comment I want to change it to. Everything else comes across the pipeline in my piped object. I get my should process. And now if I look at my object, I have my updated comment. So that all works exactly how you would expect commandlets to work with not a lot of extra work and in my opinion, a much cleaner sort of structured way of putting your code together. It's readable. It's understandable. Anything you could put a script on one screen and not have to scroll through a million. Of course, I could probably add more comments and furboss logging and all that kind of stuff. That would make it a little bit bigger, but it starts out very simple. We'll get on time there. So any questions about any of that stuff before I flip to the other half? No, okay. You're all either asleep or interested. One of the two. Okay. One o'clock. Yeah, okay. So that's great. And you're going to do this and in my case, I've got at this point with my one object, I've got a single class definition and four commandlets. If you like me and you like to separate all your scripts out into individual files for ease of coding is what you see. You've got something like this. And then you're going to add to kind of do a rinse and repeat operation. You're going to go hit the CNAME record next. You're going to do the same kind of thing with changing properties around for CNAMES are obviously a little bit different. And you're going to hit PTR and then you're going to do a fixed address and then you're going to do, you know, whatever IPAM or DHCP leases. There's no shortage of objects on your, on a typical API. So when you're done with that, you're going to have a folder full of stuff. And again, what we talked about is this is, that's great. But what we want to do is make that useful for everyone. So let's talk about now how we're going to go from a folder full of scripts and class definitions, PS1s to ultimately a module published on the PS Gallery that anyone can download, import, use, update. Let's back and do some PowerPoint because that's what we're all here for. I can do my fat magic, hot keys here. All right. No, maybe. There we go. Okay. That's where we left off. So let's talk about the release pipeline. That's my son enjoying his own release pipeline. So release pipeline, there's nothing better than just sitting back and watching a release pipeline just go. It's just, anyway, enough of that. It's not recorded. You can cut that out. Okay. So release pipeline, you hear people talk about it. Build, test, deploy or source, build, test, deploy. So we're looking at that. We want to do that. And of course we want to automate everything. So let's look at what that might mean in our case. So build. What I think in on build is at the end of a build, let's create something. Let's create a tangible thing that I can interact with. So I'm thinking I'm going to make a module. So I'm going to create my directory structure. And next one's kind of contentious. I like to concatenate all my script files into one big PSM1. I know people like to dot source. That's cool too. That's what you want to do. I don't know if one's better than the other. I like to argue about that on the internet because it's the internet. I might make some change or create or make some changes to my module manifest. If I'm going to go to the gallery, I'm going to need to have all my metadata for that, versioning information would go in there. If I'm using external help, if I'm using markdown, I'm going to turn those into that horrible worse XML, worse than XML mammal thing. And I'm going to use platypus because I don't want to do that by hand. So if I'm using inline help, that wouldn't be something I would need to do. But those are the steps I would define and at the end of my build, however I've chosen to do it, I have a module and a folder that I could take and copy and give to someone and they can import and it works just like any module would. So that's my build. Deploy is pretty simple, right? I want to deploy it to the gallery. And since I've done all my work and build, that's actually relatively trivial. But between those two things, because again, I want to automate this and I don't want to deploy a bad change, I'm going to do a test. So I'm thinking, okay, I'll fire up Pester and do some tests and because I want to do these in an isolated fashion, thinking, okay, well, everything is local until invoke rest method. And invoke rest method is where I leave and I go out to my endpoint and do stuff. When I'm not going to assume I have an endpoint, it occurred to me that running a bunch of zone and record deletions and creations on my corporate DNS appliance would probably be a bad thing. So I thought I should mock that and save my job. So that's where I started. I'll just mock invoke rest method, catch whatever comes from my, when my class calls invoke rest method, my URI string and my payload, figure out what I need to, if that worked and then return an appropriate output that my code was expecting and then do sort of an end to end test. So the problem with that is what you're really saying there, the short version of what I just said was I'll just reverse engineer my API. How hard could that be? Impossible, it turns out, unless you have access to the source code. So I actually went down that route and write a bunch of code and I'm parsing URIs and if it's a get method, I look for this syntax and if it's a post, I'm looking for this syntax and doing my hash table and all this kind of stuff and I got this big block and I thought to myself, you know, it's probably the first law of unit testing that if your test code is more complex than the code you're testing, then it's not good test code and that was where I was at. And it turned out it didn't work anyway because once I went actually to an actual appliance, I guessed wrong on my reverse engineering and it didn't work anyway. So that's generally a bad idea. If you want to break this down and do more inputs and outputs before and after invoke REST method and do true unit tests, you could probably do something like that. But what I wanted was my end-to-end test because I've got to run this against an endpoint anyway. So mocking didn't work. So if you can't beat them, join them. And I sat in a dark corner and cried for a little while and rocked back and forth and threw away all my PowerShell books, my T-shirts and then I thought to myself, what does everyone do when they have a problem they can't solve? Where did I read in the CIO magazine that all problems are solved? The cloud will solve all our problems, right? Microsoft tells us that every year. So I went to the cloud and it actually worked in this case. So what that means in this case is what I'm going to do in the cloud is build an endpoint, a new endpoint, a fresh endpoint, my endpoint for my, in my case, my appliance. I'm going to build a brand new one while my test against it because it's empty. I set it up the way I wanted it, it's mine, it's real, it's live and I control all the variables for that. So that was kind of the idea. And there's two things you need when you want to do that. I'm going to walk through kind of the overview of that here. You need, because again, we're doing this in a programmatic fashion, we do this automatically. I'm not going to be manually building anything in the portal. So we need an ARM template. I'm also not going to be manually logging into the portal to run said ARM template or doing any interactive login for PowerShell at all. So we need some non-interactive authentication information. So that is kind of the, that's the goal, right? And we're going to integrate that into our release pipeline and our build test deploy. So this is kind of what we end up with. You see I've got our build and our test deploy, right? These are our kind of our dependent code blocks. And then I'm going to stick build test environment right before test. And I'm going to add some extra stuff clean, it's just mousekeeping. And it is going to be our login. We're going to do our login there. Because really, if the login doesn't work, there's no sense in doing any of the other stuff, right? If we can't create our tests, there's no point in doing a build. So I'm going to run all this from AppVare, was my choice. And so what you end up with with AppVare, and I'll walk through these a little bit. AppVare uses a YAML file to give it instructions. You tell it what to do and you tell it what to run and set some variables and all that. And that just calls AppVare PS1 script. And then that calls your Saki. Saki is a DSL that allows you to kind of define dependent code blocks in a nice readable fashion. By the way, if this model looks familiar at all, this is all from Warren Frames blog a few years back on PSDeploy and release pipelines for PowerShell. I totally stole a lot of his code and then added some stuff to it. So if you see him, thank him. If you read that blog, I actually got, at the end, I've got a link to it. That was a really good way to kind of see an end-to-end process and get an idea of how a release pipeline should work. So if you're not already super familiar with release pipelines and all the steps, that was a great thing. So I took that and added some stuff to it. That's what we do here is take someone else's stuff and add onto it. So that's out there. You could do the same kind of thing with Jenkins or with Squid or Octopus or whatever, or potentially locally, right? Maybe I'm doing local development on a new feature. Maybe I'm adding the scene record. And before I go production and maybe accidentally deploy an unfinished thing to the gallery, I want to run some local tests. So maybe I'll add the well-named build.ps1. And that's just going to be a local script that will allow me to call the exact same production script. So I'm going to run the same build. I'm going to run the same tests, which is handy when I'm doing development on a new feature on this other branch. I'm not going to accidentally deploy it. I'll put some checks in there. But maybe I want to make sure that, okay, if it passes all these tests locally on my PC, it's still going out to the same endpoint. There's no reason why it shouldn't work when AppFair runs it. So that's kind of the idea. So let's take a look at what that looks like. And again, this is all, this is the optional part of the presentation because you can really do this however you want it. You could add eight steps or two steps or 15 or whatever. You can branch off and come back and do, sake allows you to do all kinds of really complex. But again, my theme is simplicity. So I'm just going to go in a simple sort of fashion through all of this stuff here. So let me switch over to that. That is over here. Oh, wait, no. So before I do that, that's right. I was going to talk about Azure a little bit. And promise this isn't a pitch. I hit the cloud pitches, but in this case it works. I'm going to kind of walk you through. If you're cloud averse like I was, it's handy to see this. So we need two things. That's the next thing we're going to do. I mentioned we need two things when we're dealing with Azure in an automated fashion. We need an ARM template, and then we need some sort of login information. So I'm not going to talk about ARM templates because I totally cheated. I went out to the portal, click, click, click, built my endpoint, which in my case it turned out to just be a VM with a custom image that this vendor created. It can be as complicated as your API dictates, right? It has to be some sort of weird cluster with this web front end. If you have to define all that, you go out there and you build all that. I built all that from hand, put it, give it a public IP address so I could access it, verified that it worked, and then I just did a snapshot. It's changed since I did it, but they make it super easy. Actually, let me just show you that here. They make it super easy to grab that ARM template. The ARM template that they write. Oops, just getting in the way here. There we go. So this is my test bed for this session. So in my case, I've just got, oh, this is just a pile of my resources. I've got my virtual machine, and then of course that has a network adapter and a storage account and a public IP address. That's obviously important so I can get to it. So I just come up, once I get that and I verify, manually verify that that's all working and accessible from anywhere, I can just come over to my automation script, this tab they added, and it writes the ARM template for me. It's not the prettiest code in the world, and you'll probably want to do some cleanup, but once you get it working, you don't really have to muck with that any ideally. So that's the world's shortest ARM template demo, but you can dig more into that. I think there's some sessions on ARM templates that go into a whole lot more detail on that. What I want to talk about in Azure is the second half, the authentication piece. That's kind of weird. So what I'm basically saying is I want to allow an application to authenticate automatically like a user. In the old days of AD, we would call that like a service principle. I want to authenticate as an application. You kind of have that here. So what we're going to do, we're going to go to our Azure Active Directory, and you don't need a complicated, fancy corporate P2 whatever. See, I'm using my on Microsoft, which is the free one they gave me when I logged in, and it's a free trial. Oh, what did I click on? Oh, I went back. So I'm going to come in here and what they call app registrations. I'm going to create a new web app to do my login. This gets in all kinds of weird OAuth and application. We're not going to really do any of that. We're just creating an application so we have a security context on which to authenticate. Let's come in here, a new application registration. Again, what we feel in here matters very little, and you want to give it something that you can find later. I've done this a few times. We'll call this InfoLogstiness Appliance Rest Test, something unique. Sign-on URL again, because this isn't a real web app that we're authenticating to, or test call is calling it, but we're not authenticating it. Sign-on URL doesn't really matter. You can put whatever. You can put local host. I like to put my GitHub repo for the project just to help me remember what I was doing when I see this later. I don't even think that's right, but anyway, it doesn't really matter. It just has to be a URL, because again, we're not going to use that. What we are going to use, well, we got to go find it. It's too much to ask for them to give it to us. So we'll search for it. Nope, that's not my app, apparently. Someone else's? I've done this a few times. I got some mess. What did I call it? This one here. So what we are going to use is this application ID. That is going to effectively be our username for our credential object, application ID. So we're going to copy that off, and just throw that in a notepad or whatever for later use. We'll save that. Now we need a password. We've got a username. We need a password. We're going to go to settings, go to keys. That's the GUI part's almost over. So we're going to create a new password object. Just call it whatever. We'll call it password, because we're super creative. Duration of how long you want it to last. We'll just say never. And then value will be displayed on save. So we're not going to specify the password. We're going to let Azure pick it. So we're going to save this, and it's going to give us this password. Notice it says you won't be able to retrieve this later, so you'll want to memorize that immediately. Or I guess we could, for demo sake, we'll just copy it out. But I would totally memorize that if I wasn't doing a presentation. So you're going to save that off. I'm going to memorize it. So now we've got our username and password. And we can stick that out and out there. We can stick that in wherever. Of course, it's a username and password, so we're going to talk about how to obfuscate that. We don't want to put that in our source control. That's on GitHub, or Azure login on GitHub is probably a bad idea. So that's enough of the fancy web interface. And that's great. But anything, just about anything you can do through the Azure portal, you can do through PowerShell. So let's do it through PowerShell because that's way more fun. So I've already done the interactive login here. I think unless it timed out, I mean we'll find out. So when you do these, we're going to do the same thing. We're just going to create an application registration username and password. In this case, rather than have the portal create password for us, we're going to specify it. So we're going to make a nice long password with a special character and uppercase, create a secure string from it. And we're going to call this new Azure RMED application. And same kind of stuff that we filled in, right? We give it a display name. We give it a URL. And in this case, we give it the password rather than asking us to specify it. But we're going to run that. Let's see if that's still logged in. Oh, that's right. I keep doing this over and over again. Let's change this a little bit. Again, the name doesn't matter. Oh, why not? I didn't clean that up. I got to change this, too. It's a problem with demos. They make a lot of garbage. There we go. So now we've got an application. Let me look at it. And it's just an object with some stuff in it. So that is the PowerShell version of all the pointy clicky stuff that we did. And this is better. But they match to the same thing. You've now got an application ID. And it's got a password. So you've got the makings of a credential object. So let's go through and let's do the next part, which would be to how you would actually use that to log into Azure. So we could use the information that we memorized. I mean, wrote down. But we're not. We've already got it in PowerShell here. So we're just going to use that. So we're going to create your standard PS credential object. No big deal. We're using the application ID, not GUID, and the secure passwords. Our application ID is our username. So we're going to create that. And when you do this here, you have to get the tenant ID. And there's a couple different ways of getting that. You can write it down. I heard this actually a website called, like, what is my tenant ID? And you give it a URL and it'll tell you, this is your tenant ID. Or this is their tenant ID or whoever. So that's kind of cool. We're going to use this Git Azure RM subscription. And that's just a way of getting that. We just need that to tell it where we're logging into. And then we're going to use log in Azure RM account. We're giving that credential object. And the tenant ID, we're telling it this is a service principle log in so it doesn't try to do any interactive nonsense. And that is just a trivial thing. It runs that. And then it logs in. It spits us back our information saying we're logged in. So this is the bit of code that we put in our build pipeline. We obfuscate our password, obviously. But this is the code. This is completely non-interactive. So I use the appvair encryption. Yeah. So you're just saying, yeah, I just go in and if we have time, I'll show you that. But yeah, that's super simple. So yeah, I just use that. When I'm doing the local build script, it's not appvair. I just build all those passwords ahead of time and then pipe them in through parameters. Yeah. Let's see. Where was I on that? Yeah. So I'm going to walk through what this kind of looks like just to kind of give you a look at how that looks. So I'll start with appvair. So this is the appvair.yaml file. If you haven't seen this, let me expand all my stuff here. Yeah. Well, I'm going to expand it all. So the yaml file is just a basic instruction set for appvair to kick off. When you create an appvair account, you tie it to your GitHub account. And so I'm giving you a couple things here. I'm telling it, if I have a commit message that says, update it, read me. Don't do a build. I don't need you to do anything. Just go back to sleep. I'm only doing a build on the master branch so I can do commits to any other branch without worrying about it kicking off a deploy. And then the rest of that mostly is environment variables. That's kind of its method of passing information along to the build server because this is all running on their build server, which is just a PowerShell session out on their server. So I've got my module name and my resource group, and that's used when I'm developing, you know, spinning up my Azure environment. I've got an admin password, which of course I want to obfuscate. So this is what that looks like. You get this secure string. And appvair provides that. It crypts it with your account so it can always decrypt it. And it does a pretty cool thing when you use this secure password string method. When it runs its build system and if it uses that password in any sort of visible way, it keeps track of that and it'll obfuscate so you get a bunch of stars on the output screen unless you do like write out whose password or something stupid. But it'll protect its, it keeps itself from screwing up and then, you know, lets you kind of do your own thing. But it takes a little effort. So, you know, I've got all kinds of stuff encrypted. I encrypted my tenant ID just for fun and my app login. That's one I already made, obviously. My PS Gallery key, of course, is important. You don't want anyone deploying as you. So we just add all those in there. But yeah, you have unencrypted variables, encrypted variables, they're treated the same way. $e and v colon, these all show up as environment variables when you're running your scripts. You just treat them like environment variables. And then I'm telling you to just run this test script, right? You can do a build script and a test script and you can let appvair handle those stages. I'm just calling one script and letting Socky handle all my ordering of my steps. You can do that kind of whatever you want. So that's going to call appvair PS1. All I'm kind of really doing here is setting some variables to other variables. The reason I'm doing that is I don't want to be everything to be, I don't want my Socky script to be tied directly to appvair, right? Because again, I might use Jenkins or something or my local build script. So this is kind of a shim script because I don't want to reference appvair build folder in Socky because I can't use appvair build folder if I'm not using appvair. So I have project root and artifact root and module version. I import some modules that Socky might need to run. Obviously I need Socky installed to run Socky in Pester and Platypus. So I just install some modules because again, at the end of the day, a build system is just a PowerShell environment spun up that you're running scripts against. That's where they all build system is. So I'm going to call invokeSocky and give it my script. It's going to walk through that. This is what appvair is running. And the way this is laid out, oh, that didn't minimize. That was going to do a whole big, pretty thing here. Let's see. I didn't do it. I don't know. I'm second. I got this new extension called fold. It's really cool for auto folding stuff except when it isn't, apparently. So and I wanted to kind of have this view because I want to show the layout. So domain specific language uses this concept of a task, right? So they do a sort of an inverse thing. Default always runs. The default depends on deploy and clean test environment, which is my last one. Of course, deploy depends upon test. So it's going to run test first. And test depends on build, et cetera. So it runs in a top-down fashion from a bottom-up dependency. Kind of weird. But that's kind of the idea of Socky. And you can do branches and you can do all kinds of extra dependencies. I didn't need anything too complex. And for a basic PowerShell module function, you don't need anything too complex. So let's see. So I mentioned I do my login there. Oh, no. I'm going to use my hotkey here. So I check if my build system is AppVayer. In my case, I'm doing AppVayer and local. So if I'm doing a local build, I'm just going to use an interactive login. So if my build system is AppVayer, I'm going to do my non-interactive login that we just saw with our credentials. And notice I'm referencing Azure App login ID as an environment variable. That was encrypted. But throughout there, I don't need to do anything special to tell it to decrypted. It just handles that. Same with my password. It just handles that. And then this is what we just saw. We log in with a service principal log in. I do that first because I don't want to bother doing anything else if that fails. It's all going to go south from there. Clean is just some, when I'm running iterations with a local build, you know, you got to delete some folders. So let's look at build. Build is, that's what I was talking about with, I want to build a module. So this is all my code to turn a pile of PS1s into a usable module. A couple things in here. One that's really been useful is the versioning. So AppVayer will give you a build number. And that's an exposed variable environment underscore build, build numbers and all like that. And that will increment every time a build runs. So what I'm going to do is I'm going to use that build number in my module manifest as the version number. Just automatically increment. So that way every time I do a deploy to the gallery, it goes up by one. If I want to change my major, minor version manually, if I'm, oh, this is so much better than the last one, I'll make it 1.2. You know, I can do that. But this helps me not worry about unincreasing or having, you know, versions step on versions. That's all just automatic. So that pretty works. If I ever get to whatever they, whenever that number rolls, I don't know what's going to happen. I don't know if that rolls at 99 or 999. I don't know. But I'm only like 50. So that should last me for a pretty good while. So that's one of the things I'm going to do with my update module manifest is I'm going to take, I've got a static module manifest that has everything you need to run that module. It's pretty much, most of that information is static, right? I update the copyright because it kind of feel like I should. I don't know. And then I have some variables there. The module version is really the only thing I'm changing here is I'm going to increment that based on my build number. So when I deploy it again, it's going to show up as new. Which PowerShell Gallery likes that sort of thing and PSGit likes that sort of thing. And this is where I'm going to do my platypus, right? So I create my new external help. If you're doing, you know, external help or markdown help, that's important. So that's all kind of done. And when that's, if that works, it's okay, looks at errors. If any of these bomb out, throw an error, the whole thing will stop. The dependency sort of depends upon build, but really depends upon build not failing. That's what it's actually saying in the short end. So next I'm going to build my test environment. And that is my Azure deploy, basically. So I've already done my login, which presumably if I've gotten at this point that passed. So build test environment is two things, really. I'm creating a new resource group based on an environment variable I set with a location that I set there. And then I run through, there's this command, it doesn't do a whole lot, test azure rm resource group deployment. Those names, jeez. That's going to do some basic validation on your ARM template. Give you a reasonable chance that it might work. So errors still get through, but that's something worth running. So again, I'm checking that that passes. And then I'm going to run my deployment. Now I have this ARM template that I downloaded that I let the portal write, which is just a big steam and mess. Everything's a parameter, it's like 800 parameters, and they're all static values anyway. It's ugly, but it works. So you can clean that up if you want or not. Just close your eyes and pretend it's not too bad. So I'm going to pass the resource group, obviously, to put it in. There's my template file, my azure deploy JSON. I just save that in my source control there. There are any numbers of parameters I could pass to it. The only thing that I want to pass to it, really, because nothing really changes, but I do want to pass my password to it. Because again, I don't want to put a password in my ARM template any more than I want to put a password in the rest of my code. So I'm going to pass that password, and I'm going to let AppVare decrypt that admin password. And that's going to happen all kind of behind the scenes. So that's going to deploy my azure environment, and that takes about eight minutes. So that's not bad. It's going to look for that status, that provisioning state. So if that's succeeded, it's then going to continue on. If that fails, of course, it's going to stop because there's no reason to run tests against an environment that doesn't exist. My tests are just your standard Pester tests. And so that's just going to gather some data and run invoke Pester. And the way I've organized the tests, I won't go into that because we don't have three hours. I wrote a lot of tests. It's kind of embarrassing. But the idea there, because I now have a full environment, I can do full tests. I don't need to test this piece or this piece. I can just say, create a new DNSA record with the name of server one. Get the DNSA record. Is the name server one? Okay. Good. Is the IP address what I said it should be? Good. Okay. Now take server one and delete it. Did it delete? Yes. So I test the creation, and then I can test the deletion. In between that, I can test all the querying. There are two server ones with an IP address of whatever. And again, I don't have to do anything really complicated because I just have an environment to run against. I'm not really doing weird unit tests. I just run the full thing. Okay. And so that's that. And then deploy is really, is pretty straightforward. We've actually made it this far. Publish module. That's really it. Right? I've got my module name. It's in my environment. And then my API key is my encrypted string that I let out there and crypt for me. That's really how it is. And then because I'm a cheapskate, I added clean test environment. And that's going to go back to Azure like two minutes later after my tests are run and delete everything. Well, delete everything in that resource group, not everything else. And that's really cool. Not to get all weird and evangelize-y, but basically what I've done there is created an environment automatically, run 400 tests or some un-gully amount of tests against it in like three minutes, and then delete it. And so what I pay for is that three minutes of runtime, which they usually round down to nothing, which is nice. Yeah. So that's kind of that. How are we doing on time? I started this late. When is this in? 12.45? Okay. So yeah, we're about on track. So any questions on any of that stuff? Okay. Now, a lot of this is up to you how you want to do it. And it's very much up to design choices. I kind of wanted to point out, you know, kind of walk through and end-to-end things. Just seeing that from start to finish is, for me, it was really helpful to see that I didn't really get the individual pieces until I kind of saw how it all walked through. So let me show you. Sorry. Yes? Can we switch to the AppVayer? Yes. I was actually going to show you that next. Yeah. We got a few minutes. So let's see AppVayer. Now, that was super simple. It's almost disappointingly so. So I'm going to AppVayer. I've logged in. You can link it with a number of different accounts. I've linked it to my GitHub account. That seemed to make it sense. So this is me logged in to AppVayer. And I'm looking at, in this case, I'm looking at my latest build. Let me show you this here first here. So this is a history of all the builds that have been run. And you see over here it's got, it has the build number. So it's pulled in the 1.1 and the 1.2 is for me. I put that in my module manifest. Actually, I think I put it in a variable first. It came for me somewhere. And I had that. And so I control the incrementing of those codes. But then you see AppVayer is doing 60, 61, 62, 63. So I'm just grabbing that. It's handling that incrementation. The message here is just whatever my commit message happened to be for that master branch. So you can see here I added an IB lease object to handle DHCP leases and define them as a class so then I incremented that. So if we look at one of those here, we'll look at this latest one here. Again, because all a build system is, it's just someone else's PowerShell session running your scripts for you. And so what it does, the first thing it does is it, any build system will do this. It uses get to clone the repository. So it gets a fresh copy of all the data in a local folder. And then it has its YAML that calls the PS1. The output of this is really all up to you. This is probably one of those times when it's OK to use write host because you're not outputting to anything but the screen here. You're not going to pipe to anything. So I have these lines, right? It just outputs lines to separate the sections. Ideally, you'd put in some good information about where it was in the process. I didn't do any of that. So there's not a whole lot in here. So but in it, I would do a login and I would probably say logging into Azure. Login has succeeded. But this is, Sake is doing this. So Sake is telling you executing the init task that is defined in that block. And clean executing build. So there's some output I just didn't catch again. This is just sort of, go away a little. So this is again, if you're running Sake locally, this is what you would see. Just like on the build system, this is what you see. It's just a PowerShell session. So you can see not a whole lot of output when I run my tests. Here's where you see all my Pester tests. So I guess I could talk about these real quick. So I've broken these up into sections, right? So for each object I've created, like in this case, I've got this web session test and this IB view. This is an infobox construct. But I've got networks and DNS zones and DNS records. So you can see I'm doing, I've got this, the way I did it to save myself from typing is I do a, in order to do a remove test, I need an object. And in order to do a get test, I need an object. So I'm going to run all my create tests first. I'm going to do two things with that. I'm going to create all the objects so that I can use them later. And I'm actually going to test that they were created correctly. So I do my create tests for each of my records. And then I'm going to run my queries, query against all the different kinds of permutations of how to find stuff. And then at the end I'm going to run all my deletes. And I didn't write really fancy Pester tests. So I just wrote a lot of them because that should be the same thing, right? So and then I wrote some more and then I got tired and then I wrote some more and then I deleted some stuff and I wrote some more and then my test complete. And then at the end, this is all soccer here. Soccer gives you this little report. And so it'll tell you how long each section took. So you can see it, the build test environment, about eight minutes to deploy to Azure. My tests took about just under four minutes to run. And then I did my clean. Deploy takes no time at all and then clean. So that's soccer handling most of that output. And I've various just sort of running that on your behalf. Jenkins and all those other ones, they probably work in a similar sort of fashion. Let me show you while we're in here. I think we've got a few minutes. The encryption thing is so you go to your account because it's based upon your account and you have all your stuff for your standard account stuff. They'll let you give them money if you want to. But you don't have to. This is all free. I should mention that. The app there thing for basic setup is completely free. So they have this section here, encrypt data. So you're going to hear, and this is just encrypting it for your account. So for any project that you have, this works. You just put in whatever, a nice secure password that no one would ever use, encrypt it, and it spits out that encrypted value. And it also shows you the syntax for your YAML. So you just copy and paste that, stick that straight in your YAML, probably change the name of the variable. You wouldn't want to have more than one of those. But yeah, so that's really all there is. App there makes that super simple. If you're going to run a local build system, obviously you wouldn't use those. That's going to be unique to App there. But that's where that shim script comes in, right? You're going to pull those in and then set the values to something that you expect to be more modular. Let's see. That, I believe, is, well, maybe. Okay, come on. Yeah, so I like to do all that again. Okay. That's, I guess, the review. That is what we talked about. I'm going to walk through the build script. It's basically the same kind of thing as the App there script. It's just setting some variables and taking more interactive input. And that's it. That's what I have. I am now exhausted and passed out on the couch. I've got a couple of links up here. The top one is the link to this presentation. That repository has this slide deck. It has the demo code and all that kind of stuff. The next one is the actual project that I based this on. So if you actually have an Infoblox and you care, check that one out. It's on the, it's actually on the gallery as, I think I called it Infoblox Command. There's a few of them out there. But if you want to check out the larger set of code, Infoblox Classy is where that's at. That last link is Warren's blog on the PS deploy. It's a little old. I mentioned to him yesterday, I was like, hey, I loved your blog. I'm going to actually use that website and come in. So, I don't know, take it for what you will, but he had some links in there to some other work and kind of how this release pipeline lays out. And it was really helpful for me to kind of understand and I based a lot of what I did off of that. So I want to include that link. So that's it. Any questions? We're finishing a little early, but yes. I have a little novice question. What did that Sel and the class come up with? That's the problem with something like this is because the classes have methods, APIs have method, invoke rest method has methods, so it's hard to talk about because it gets muddled real quick. But anyway, so that's it. That's all I have, I guess. We can be the first ones in the lunch line, so that's cool. Thank you all very much. Please. Thank you.
right? And that's for readability and that's for I started looking at doing with the REST API and class definitions as kind of that API interface. know all the properties. It has to be of that copyright on that, both the picture and the for each of those. All right? So I'm going to they are similar to the class makes sense because just doing this in PowerShell without classes is parameter validation when we're wrapping Not any of the types that I've used. No. Yeah, I potentially multiple objects. Or multiple objects we're kind of, and again, this is a design we're going to hide all that from the user so and properties and methods are defined on it at that separation of code is because this is the removal command with just over 50 lines of code. kind of cover. So set is a similar and kind of a module published on the PS Gallery that anyone route and write a bunch of code and I'm parsing of unit testing that if your test code is more in my case, my appliance. I'm going to build a stick build test environment right before test. get an idea of how a release pipeline should I'm doing local development on a new feature. again, this is all, this is the optional part of I was going to talk about Azure a little bit. And encrypted. I encrypted my tenant ID just for fun run my deployment. Now I have this ARM template scenes. So that's going to deploy my azure there's no reason to run tests against an your standard Pester tests. And so that's just see. It's just a PowerShell session. So you can your account. So for any project that you have, larger set of code, Infoblox Classy is where