Developing with PowerShell Classes: Here be Dragons
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Olin, Brandon
Classes are a powerful capability in PowerShell and are meant to provide a more familiar developing experience to more traditional programmers. You will not experience calm skies and quiet seas though. This talk will show you where the dragons lurk and help you navigate the stormy seas. Classes were introduced in PowerShell 5 and mainly to aid in writing DSC resources but can be used with non-DSC modules as well. This talk will go over some of the hidden gotchas when developing with classes including: * Importing issues * Verbose / Debug oddities * Sharing classes between modules I've developed and released a large PowerShell project (PoshBot) that primarily consists of PowerShell classes. While I could have written the module using traditional functions and cmdlets, I wanted an excuse to try classes out on a new project. Using classes was not a smooth experience and I want to share some of my challenges.
 So afternoon everyone. My name is Brandon Olin, Dev Black Ops on Twitter. This session is about developing with PowerShell classes and here be dragons. And what this really means is what are some of the gotchas that you're more than likely going to find, or at least these are all the things that I found when working with classes and developing with them and creating modules with them. So hopefully this is useful to you and this will help guide you when you actually, if you do start using them, what not to do. Or you know if you do like pain and suffering, go ahead. These links go to speaker deck and to the GitHub repo for these demos. The slide, this is like five slides so it's useless. But the GitHub repo will have all the demo code that I have here. So here's our agenda. What are PowerShell classes? I'm going to give a very brief overview of what they are. This session is not really about how to write classes. If you went to Jeremy's session about his info blocks, he did a good overview of more in depth about classes themselves and what you can do with them. This is more about, okay, after you've decided to use them and you have them, what are the headaches you're probably going to run into importing them, sharing them with other modules and all that kind of stuff. So hopefully these kind of tie I think pretty well together. I'll go over a little bit about why you would use these classes. Really the bulk of this is all these gotchas and there's a few more of these about what you're probably going to encounter. And then hopefully we got a little time for Q&A at the end. So here's an example or two examples of what a really basic class is. On the left hand side you see we have a class that defines what a car will look like and a couple of basic properties about that car, manufacturer, model and year and a method that operates on that class called drive and it obviously does what it says it does. And at the very bottom is how you would instantiate that class with the new method or one of the ways you can do this. On the right hand side is a little bit more advanced stuff where you can do with classes and this is where you actually start getting more of the benefit of using them is code reuse is I want to create a new class called the Tesla and obviously it's going to inherit a lot of the properties from a car class. It's also going to define a couple new ones, max speed, range and so forth. And then we're going to set those when we on the default constructor on this class we're going to set some properties on it. This is just really a basic example of what you would do with a class but why would you want to actually use classes? I kind of think of it as once you start using PowerShell to create applications versus admin scripts that are pretty linear you start, you're starting to move morph into the realm of development and you start needing to think about some of these more developer type terms. I'm not a developer I know a little C sharp, I know some JavaScript, I hate it. I didn't go to college, I'm a dumb marine so yeah. I've just learned stuff in my trial and error and sure force it will. But the main reasons you would use a class is separation of concerns. You want this instance of a class to do one thing, another class has a different set of concerns, it does something entirely differently. You don't want this code to bleed into each other and this is really about just code reuse and keep your things tidy and orderly and this is much more important when you start developing big applications. You need to cordon off what these things do for each other. That's it for the slides. So demo is really about now you know everything you need to know about how to use a class, right? Okay. I'm going to run through, hopefully I have time to run through these 12 scenarios about classes and this intentionally shows you a lot of red and this is a to hide my errors and to fool you but this is really to show you what you're probably going to see once you start using classes and try to work with them. This is the stuff you're going to see and how to work around them. So the first scenario here is we all know what import module does, imports modules. Let's say I have a class in a module, let's look at this, this is my class. Very basic module, has a manifest, nothing special there, nothing special really in the PSM1 it has a class called foo. We want to use that class in our PowerSol session. Well you would think, we've all done this, we want to use a function or something of a module, we import the module and expect that to work. You think that's going to work? No, of course it doesn't work. Unable to find type foo. Why is that? Does anyone actually know the true reason why that doesn't work? I actually don't know the true technical reason if Bruce Payette was in here, you'd probably tell me why. I just know it doesn't export the class. If you actually dig into the PowerSol code for import module and using module, you'll probably find out why that is. But the gist of it is, the raw class and type is not exposed when you import the module. How do we work around that? Stealing my thunder. Using class which was introduced in PowerSol 5 is how you would use a module with the class in it. Let's see what this does. There's all kinds of gotchas with using module and I'll go over some of them. This worked. This is how you would use a module, it'll import the module and expose that class to your session and now I have access to the raw class type and I can instantiate an instance of it. You can see that it did actually import the MyClass module. Great, right? Using modules to answer for all of this. No. This is how you were using in Infobox module is you can hide all your classes internally to the module and expose them with just normal PowerSol functions or commandlets. Let's see what this does. Let's look at this car module. This is what I tend to do with my modules is I like to break them up into small little pieces. I have public and private classes and separate files. I think most people do that probably now. Is that true? I like to be tied at. I don't like a 10,000 line PSM1. Let's see if this works. I have a class folder. I have a simple class called car that's in that. My PSM1 dot source is that class file when it imports and then I have a normal PowerSol function that will create an instance of that car, set some properties and then return it. This is my, this new car function is my way of interacting with this class. You can see that worked. I imported the module. I created a new class. I should say an instance of a class. Do a get member. I can see it's car. I can do everything I want. This is a normal PowerSol object at this point. You can see that. I can pipe that and format list, format table, whatever you want to do with that like any other object. That gets around one problem with classes not being exposed from a module. You expose them with a function which means you also have to create a function that knew something to create an instance of your class and give it back to you. These scenarios kind of go in order of complexity. Here's a gnarly one. Let's say I have a car class and I have a Tesla or a car module and a Tesla module. This Tesla module is completely separate from the car but I want to extend the car for whatever reason. I have someone expose this, created this really nice class. I want to be able to use it in my stuff in my own module. I want to be able to inherit from it. Can you do that? Let's actually just put these in my path real quick. I'm going to import Tesla and it doesn't work. Why is that? Anyone have a guess? This is how you would normally define a dependency in a module, right, in the manifest. Do you require modules? I have that. I require the module by then at work. It should have imported car before it loaded Tesla. What's the set? It has no idea. It's the same problem as import module. Internally, the power shell required module is probably just running import module with some variation of it. You run into the same issue. This theme of all these things, a lot of these things stem from that core problem right there. The dragon in this is that required modules does not expose the classes just like import module does. Same problem as scenario one. You also find with developing with classes, at least I'll be setting up a class and tweaking it. Oftentimes, it's in a weird state and I just end up restarting my session just to make everything same. Let's add, hello. We'll fix the Tesla module. We had a hard dependency on the car module with the required modules in the manifest. Let's remove that because we know that doesn't work. Let's comment this out. We'll create a soft dependency. You really shouldn't do this. Now, inside when I import that module, I'm going to assume the car is already on a machine. I have to assume that here. There's other issues with doing this. Now, things like PSGaller stuff, you try to publish these to the PowerShell Gallery. You're not telling the gallery that you have a dependency on this other module. That's completely hidden from those. You can leave the required in your manifest. I don't know if that'll work. Let's see if I have time to test that. I think I had that already. Double check. Let's try to import that. It doesn't work. Anyone have a clue why? I'm using module. I have it as the first executable thing in my script. That's another requirement with using module. Let's go back to the car. Dot-sourced it. Classes aren't exposed even with using module if they're dot-sourced from the PSM1. They have to be in the PSM1. That sucks because I don't like 10,000-line PSM1 files. We've got to do what we've got to do. Let's take, let's just pull this out of there and let's stick it in here. Let's try this again. Save, save, save. It works. Now I can do new car, new Tesla. There you go. That sucks. It's problematic. I don't have, I no longer have a hard dependency on the car module. It's dangerous for me to publish this because someone else pulling it down is not going to get that dependency. And again, it's the same core problem as the classes and using module. They need to be in the PSM1. Not dot-sourced. So let's try this a different way. Let's try to create, to have a hard dependency on the module with required modules and use a class in the PSM1. Actually, that's what I did there. Do I have a hard dependency in required module? No. Sorry. Put that back. Car, the car module is now correct. The class is in the PSM1. Let's take this using module out and do this how we think it should work. Anyone know why this, when you paste, is really slow? Anyone see that? Anyone have that too? Yeah. Yeah. Yeah. Yeah. Doesn't work. So this is what we would think we would need to do is have the required modules. Again, the core problem is that required modules is not running using module under the covers. It's using import module and we already know that doesn't work. So no joy. Required modules is not using module. Soft dependency on car module. So we already did that one. I believe. Yes, we did. Yeah. This works. It's yuck though. Like I said, things like PowerShell Galley, we won't know about this dependency. If you try to import it, you have to assume that that person already has that dependent module on the box and that's not good. Everyone clear on that? That's kind of a long set of scenarios there. I should say that anyone not clear. So let's go, let's restart this just in case. So we have classes. Tesla and car and I want to import this thing. Let's just dot source this and see if it works. Is it going to work? No, of course it's not. Normally, you know, functions, PowerShell functions, you can write them in any order. You know, the PowerShell is going to parse the whole thing before it does anything with them. Not so with classes. Order matters. So let's fix that. And there you go. That works. This is a real pain in the butt once you have a fairly advanced module and lots of classes and you need the things depend on each other and you need to order them such. I don't know. I know someone that I've worked with on a way to kind of like recursively try to import classes and then eventually it'll work. Yeah, just keep on going until you've run out of exceptions. And that actually does work but that's, yeah. Yeah, or I told them I was like you could probably use like the abstract syntax tree and parse this stuff and then do a bunch of magic on your own to parse all these files, come up with a graph of things to import and then import them. I don't even think you can get to the AST without like it tries to compile. Oh, does it? So you can get to that AST. I haven't even tried it so, okay. I think we attempted something like that. Nice. Okay, so don't do that. So this is not really a dragon. This is really just an FYI with classes. You want to hide properties from a class. Maybe they're internal to the class. You don't want people to muck with them. But hidden, so you do that with this hidden keyword on a property. So we have a class. Let's create a new instance of it. So we have our name property. It's public. We don't see our secret in our super secret password Hunter 2. But we can always get to this if we really want to with force. And we can see the secret property there. You can also see a bunch of other methods that PowerShell has created for you on this class and get and set. And this is internally how these properties are being set. So can I actually see that secret? One thing it does not do is give you a tab completion on these. So if I do f. I hit tab, I should tab expand this and see this property like you would normally expect. Hidden properties don't do that. So you have to know what you're looking for and you can see it. And you can also set it. Of course, it's not a locked property. So don't use this. If you don't use this to store secrets that you're thinking that you're going to hide from people. If people, you know, and this is the same in C sharp, you know, hidden properties are not meant to hide things. They're meant to tell people not to use them. They're internal to the class and their implementation details of the class are like private APIs. And you run the risk of breaking things when you try to use internal things because it's on them to change them. They give you public properties to use. Don't use the internal ones because they're free to change them. So that's really the gist of hidden properties. Not the hide secrets from you. Just to tell the developer or the user, this is stuff that you don't, this is just inside baseball stuff. Don't use it. So not really a dragon, but, you know, they're not private and even private in C sharp is not really private. You can still get to them. It's just hidden and not very well. I'm going to do one on time. Okay. Verbose and debug. This is not really also a dragon, but this is just another stuff that is more of an FY about working with classes. I got this demo class from a fellow on Stack Overflow. But this test kind of gives you a nice rundown of how verbose and debug work inside classes. So let's import this. So we have a class and then two functions. So inside these functions, we have a normal function. It's going to create a new instance of that class, run a method on it. Pretty simple. And we have an advanced function with command of binding that gives us verbose and debug and what if and all that kind of fun stuff. All the stuff we should be using for every single function we write, right? Right. I don't see any reason not to use advanced functions personally. They just give you a lot of stuff for free. So that works just like we would expect it. We create a new instance, pass it a property. Let's try the advanced verbose works like we would expect it to. You can see my verbose messages in my constructor and in my method. Debug should work the same way. It appears to. It ran my debug message right there. So that's cool. But there's always a but. Well, actually this works. So we'll just mainly set our verbose preference in our session. So we're not going to wrap it around a function. And we'll create a new instance. I don't have verbose turned on. I shouldn't expect any output and that's correct. Let's turn it on and run that again. We get our verbose message like we would expect. Does debug work? They worked in the function. So we'll set it to inquire. This will also work with the, I should say not work with continue. Where's my debug message? The debug is turned on. Verbose seemed to work. Not debug. Did anyone know why? Or have I seen this? Because I don't know the answer. I think this is a bug. So does verbose. They're both in separate streams. Well, I mean, we do continue. It won't do that, right? I don't see it. So I think this is a bug. I'm just curious if anyone else has seen this. I'll probably raise this. But to me, that should work just like verbose. So that is a potential gotcha. If you want to manually work these classes and you want to turn on debug, you got to wrap it in a function and that sucks for debugging purposes. All right. So requires. How many people use a requires module? Okay. Some people. I personally don't use it. I think there's some problems with it. This is just yet another one of them. I'll show you why. So requires at the beginning. Let me run this in my session. Make sure I'm all fresh. Requires also does not import the module correctly. Using modules is the only way to do this. That sucks. So requires not going to happen. Let's use that. Let's do that. That worked. I should dot source this. And I got my instance. So using module, don't use it if you want to use work directly with classes. It's not going to work. Same problem as one scenario for something like that. The other problem with requires modules is that it tends to swallow exceptions if you're trying to import a module and that module doesn't, it fails to import and is thrown errors. Your script, you're not going to see the actual reason why that module didn't import. It's hiding stuff from you. You can lead to some debugging pain if you try to use requires. So requires doesn't import classes. So run me first again. Refresh. So I have two modules here called fruit. Two versions of it. One and two. You don't do anything particularly special. They have a couple classes in there about a fruit based class and an apple that extends it. And then I got version two. The only real difference between these is I think version two added calories as a property. Let's close that. Okay. So I should be able to see them here. So I added that. I can see version one and two. That's great. I'll, using module because we know that's the correct way to do these things. I'm going to import fruit. But hey, I got two versions of it. Which one did I import? I imported version two the latest like you would expect if you didn't specify a version and you have multiple versions on the machine. It's going to import the latest. What if I want to import an older one? What's going to happen? Do you think that's going to work? Of course not. Yeah, there's a theme here, right? So I got that. I got that. So anyone know what module specifications are? Where you can basically have a hash table of the name and the version and you can use this with import module and just give it that hash table to import that specific one. Do you think that would work? It does kind of. It imported the module correctly. Why is this here? I said version two. Well, version that you think it actually loaded. Oh, well, sorry. I wanted version one. Why is version two here? Strike that reverse it. I restarted my session, didn't I? Did I? Okay. Why do I have two? Why did two get loaded? I don't know. I told it one. No, I told it one. I want one. The module specification is giving you a specific version to import. No, it'll load the latest always. Yeah. So that's not cool. Why the latest version? I think this is also a bug. I got to do some more investigation, try to figure out some other edge cases to see why this is working this way. But module specifications don't work or don't appear to work anyway. Not the way you would think. So let's look at a more complex module. Fittingly named complex module. This is when I read modules. This is how my common structure and probably most of it, a lot of people here is public and private. Maybe classes if you have one, you got your manifest and PSM one and PSM one is dot source of stuff. Or maybe it does a couple initialization stuff. So I have a build script that just kicks off Saki. Anyone not know what Saki is? Okay. It's a build, it's a task framework, kind of like make, rake, bake. I think there's a bake. There's one of these for every language. They always sound like ache except for Saki because PSA is hard sounding. Saki sounds cooler. But it's a build automation framework where you can set up tasks and dependencies. So you have to, you know, usually for compiling code. I use it for other things as well. As a side, I am, as of about six months ago, I'm now the maintainer of Saki. It's gone through a couple maintainers over the years. I'm the current red pirate Roberts for now anyway. And if you're interested in Saki, you can hit me up after this. But I have a build script that this is going to run Saki and I can look at my Saki task. I'm not going to go over too much of this, but basically I have a task to clean my output folder, just wipe everything. I have a task to compile the module. You know, we're not really compiling stuff in PowerShell, but really what I'm doing here is just gather in up all these source files and just shoving them into a PSM1, just concatenate them all. Because we know, we saw from other scenarios, classes need to be in the PSM1. I hate having the giant PSM1. I want to keep everything in source separated. So when I build my module, I just load up everything and put it in the PSM1 at build time. The other caveat to this, so I should say, these are my classes. We have a planet-based class. We have Mars and Earth. They inherit from planet. We know that order matters. So the order in which you concatenate them in the PSM1 also matters. And this is pretty simple with three classes, but my POSPOT module, which I'll talk about on Thursday, has like 30 different dependencies on different ones. And initially, what I was thinking was, oh, well, I'll just order them 01, 02, 03, and I'll just sort them and concatenate them in order. And I was like, oh, my God, this is just basic and go to 10 and 20 and 30, and I want to insert a 15. And what if I have 01 and 02, and then I need to put one in the middle, 01.5? And I was like, well, this is just stupid. So I quickly decided not to depend on the file names to order these. I'm just brute-forcing it. I just defined them in the order that I know that they're going to import. It works, you know, for my case. But we have a module, sorry, an array of files. We're going to concatenate them and just add them to a PSM1 file. So we do the same thing with our private and public functions. The order of these don't matter, just like normal PowerShell. So let's see what this does. So it created a bin folder, the version of the module I'm working with. I got my manifest, and I got my nice big PSM1. In my possible model, this is like 8,000 lines. So this is my way of solving some of these problems with modules. Some of it's just brute-force. Yeah. That's kind of like the problem he was talking about. My solution or potential solution was maybe looking at AST and inspecting these files to figure out what depends on what and create a dependency graph of the order dynamically. That sounds great. Yeah. It works. Yeah. A lot of the stuff is it works. Yeah. Yeah. Yeah. They just suck. Yeah. I have a thanks for that. I've looked at some of the PowerShell code for the using, and I know that parse time thing. I didn't know the technical reasons on why it's different and why classes aren't exposed. I haven't got a good explanation about why that's the case and why using modules needed in the first place. Yeah. Some custom loader. Ideally, this stuff is a hand of the PowerShell itself. That's really the end. This is really just trying to highlight issues that I find with these. I already know the classes are not 100 percent. They got added to five. I don't think they've really changed in six. I don't know what the roadmap is on classes and what they're going to fix. You could potentially use a getPike data to see if the pipe exists in your built script and if it doesn't check to see if there's a file that contains it. I got a few more minutes here. That's my compiled model. That's how I do it. I just mainly create a big monolithic PSM1. I ship it. But the same problem is with using module apply. In that case, you have to use using module if you want to get access to those raw classes directly. These two are a little extra. Let's see if I had time and I do. Classes. Anyone know what an abstract class is in PowerShell? I should say in C-Sharp. The gist of it is that, let me see if I got an example right here. I'm not a developer. This is my layman's term. An abstract class is not a thing that you actually create. You don't create an instance of an animal. That doesn't make any sense. You create an instance of a cat and a dog. Those are subtypes of an animal. It makes no logical sense to create an animal. That's what an abstract class is. It's a lot of base functionality that you would then share in subsequent classes, but it makes no logical sense to create that base class. Things wouldn't inherit from it. That's my layman's term for what an abstract class is. Sorry, this PowerShell doesn't support them. You can create a janky version of it. Let's create an animal class. We have an animal class. Here is a way to, and this is a runtime thing. This is not a compile time thing. I say, hey, if I try to ever try to create an instance of an animal, don't allow me. I'm going to throw an error. When the animal class gets instantiated and this constructor gets run, I'm going to find out what my current type is and I say, well, if it's animal, don't do this. This is mimicking a little bit of the behavior of an abstract class. In C-sharp, this would be a compile time error if you try to create an instance of it. This is a runtime check, though. This created another class that inherits from animal. We can obviously create a, well, let's try to create an animal. No, we can't. My error says, no, animals invalid. You have to inherit. Can I create a cow? Yes, I can. Can I make it move? Yes, I can. That's a way to mimic abstract classes. This is, I don't know if there's a way to do that in PowerShell. You can mimic them sort of if you want to do this. It's not strictly needed. Again, abstract classes are things that you don't actually create or would ever want to create an instance of it themselves. Here's a little bit more about the internals of classes. I have this MyClass and I want to pass it a value. In normal PowerShell and command list and functions, you do parameter validation for string length and matching this regex or something like that. How do you do that on a class method? You can't put parameter validation on this parameter that you're passing in. Here's an, I'm not necessarily advocating for this, but this is something you can play with. What this does is rely on some magic PowerShell stuff. Here's the link to where I found this. Let's create a new instance. I got a little homegrown parameter validation here. Why is that working? PowerShell is attempting to cast the string that I passed in Brandon or BR to make a new instance of validated name. When the default valid, the constructor of validated name gets created, at that point, it's using the normal parameter validation to set the value of that property. This is relying on PowerShell's casting from one type to another to do a little janky parameter validation directly on classes. I'm not necessarily advocating the way they do this. I would just wrap them in normal PowerShell functions and just do it the normal way. This is something I just saw that was interesting out there. It doesn't always work though. This is a string. This is a valid string. I'm passing string. I expect this thing to be a string. I passed it in integer. Well, integers can get cast to strings when it needs to. PowerShell is going to want to do what it wants to do to help you out. It thinks it's being nice and it's not. If I pass this string, it'll just work. It should just work the same. Oh, like this. Let's see. Yeah, this is the same thing. Yeah. No joy. We can't do it. We can add nice things. I don't think so. Yeah. Yeah, we're wrapping up right now. Okay. Okay. This is some of the links that I have, some of the projects that I'm working on. You can see Saki, Poshpot, which I'll talk about later, OVF, Watchman, my all-give repos. It's not? Okay, I'll look at that. Thanks. Thank you. Thank you.
 So afternoon everyone. My name is Brandon Olin, Dev Black Ops on Twitter. This session  is about developing with PowerShell classes and here be dragons. And what this really  means is what are some of the gotchas that you're more than likely going to find, or  at least these are all the things that I found when working with classes and developing  with them and creating modules with them. So hopefully this is useful to you and this  will help guide you when you actually, if you do start using them, what not to do. Or  you know if you do like pain and suffering, go ahead. These links go to speaker deck and  to the GitHub repo for these demos. The slide, this is like five slides so it's useless.  But the GitHub repo will have all the demo code that I have here. So here's our agenda.  What are PowerShell classes? I'm going to give a very brief overview of what they are.  This session is not really about how to write classes. If you went to Jeremy's session about  his info blocks, he did a good overview of more in depth about classes themselves and  what you can do with them. This is more about, okay, after you've decided to use them and  you have them, what are the headaches you're probably going to run into importing them,  sharing them with other modules and all that kind of stuff. So hopefully these kind of  tie I think pretty well together. I'll go over a little bit about why you would use  these classes. Really the bulk of this is all these gotchas and there's a few more of  these about what you're probably going to encounter. And then hopefully we got a little  time for Q&A at the end. So here's an example or two examples of what a really basic class  is. On the left hand side you see we have a class that defines what a car will look  like and a couple of basic properties about that car, manufacturer, model and year and  a method that operates on that class called drive and it obviously does what it says it  does. And at the very bottom is how you would instantiate that class with the new method  or one of the ways you can do this. On the right hand side is a little bit more advanced  stuff where you can do with classes and this is where you actually start getting more of  the benefit of using them is code reuse is I want to create a new class called the Tesla  and obviously it's going to inherit a lot of the properties from a car class. It's also  going to define a couple new ones, max speed, range and so forth. And then we're going to  set those when we on the default constructor on this class we're going to set some properties  on it. This is just really a basic example of what you would do with a class but why  would you want to actually use classes? I kind of think of it as once you start using  PowerShell to create applications versus admin scripts that are pretty linear you start,  you're starting to move morph into the realm of development and you start needing to think  about some of these more developer type terms. I'm not a developer I know a little C sharp,  I know some JavaScript, I hate it. I didn't go to college, I'm a dumb marine so yeah.  I've just learned stuff in my trial and error and sure force it will. But the main reasons  you would use a class is separation of concerns. You want this instance of a class to do one  thing, another class has a different set of concerns, it does something entirely differently.  You don't want this code to bleed into each other and this is really about just code reuse  and keep your things tidy and orderly and this is much more important when you start  developing big applications. You need to cordon off what these things do for each other. That's  it for the slides. So demo is really about now you know everything you need to know about  how to use a class, right? Okay. I'm going to run through, hopefully I have time to run  through these 12 scenarios about classes and this intentionally shows you a lot of red  and this is a to hide my errors and to fool you but this is really to show you what you're  probably going to see once you start using classes and try to work with them. This is  the stuff you're going to see and how to work around them. So the first scenario here is  we all know what import module does, imports modules. Let's say I have a class in a module,  let's look at this, this is my class. Very basic module, has a manifest, nothing special  there, nothing special really in the PSM1 it has a class called foo. We want to use  that class in our PowerSol session. Well you would think, we've all done this, we want  to use a function or something of a module, we import the module and expect that to work.  You think that's going to work? No, of course it doesn't work. Unable to find type foo.  Why is that? Does anyone actually know the true reason why that doesn't work?  I actually don't know the true technical reason if Bruce Payette was in here, you'd probably  tell me why. I just know it doesn't export the class. If you actually dig into the PowerSol  code for import module and using module, you'll probably find out why that is. But the gist  of it is, the raw class and type is not exposed when you import the module. How do we work  around that? Stealing my thunder. Using class which was introduced in PowerSol 5 is how  you would use a module with the class in it. Let's see what this does. There's all kinds  of gotchas with using module and I'll go over some of them. This worked. This is how you  would use a module, it'll import the module and expose that class to your session and  now I have access to the raw class type and I can instantiate an instance of it. You can  see that it did actually import the MyClass module. Great, right? Using modules to answer  for all of this. No. This is how you were using in Infobox module is you can hide all  your classes internally to the module and expose them with just normal PowerSol functions  or commandlets. Let's see what this does. Let's look at this car module. This is what  I tend to do with my modules is I like to break them up into small little pieces. I have  public and private classes and separate files. I think most people do that probably now.  Is that true? I like to be tied at. I don't like a 10,000 line PSM1. Let's see if this  works. I have a class folder. I have a simple class called car that's in that. My PSM1 dot  source is that class file when it imports and then I have a normal PowerSol function  that will create an instance of that car, set some properties and then return it. This  is my, this new car function is my way of interacting with this class. You can see that  worked. I imported the module. I created a new class. I should say an instance of a  class. Do a get member. I can see it's car. I can do everything I want. This is a normal  PowerSol object at this point. You can see that. I can pipe that and format list, format  table, whatever you want to do with that like any other object. That gets around one problem  with classes not being exposed from a module. You expose them with a function which means  you also have to create a function that knew something to create an instance of your class  and give it back to you. These scenarios kind of go in order of complexity. Here's a gnarly  one. Let's say I have a car class and I have a Tesla or a car module and a Tesla module.  This Tesla module is completely separate from the car but I want to extend the car for whatever  reason. I have someone expose this, created this really nice class. I want to be able  to use it in my stuff in my own module. I want to be able to inherit from it. Can you  do that? Let's actually just put these in my path real quick. I'm going to import Tesla  and it doesn't work. Why is that? Anyone have a guess? This is how you would normally define  a dependency in a module, right, in the manifest. Do you require modules? I have that. I require  the module by then at work. It should have imported car before it loaded Tesla. What's  the set? It has no idea. It's the same problem as import module. Internally, the power shell  required module is probably just running import module with some variation of it. You run  into the same issue. This theme of all these things, a lot of these things stem from that  core problem right there. The dragon in this is that required modules does not expose the  classes just like import module does. Same problem as scenario one. You also find with  developing with classes, at least I'll be setting up a class and tweaking it. Oftentimes,  it's in a weird state and I just end up restarting my session just to make everything same. Let's  add, hello. We'll fix the Tesla module. We had a hard dependency on the car module with  the required modules in the manifest. Let's remove that because we know that doesn't work.  Let's comment this out. We'll create a soft dependency. You really shouldn't do this.  Now, inside when I import that module, I'm going to assume the car is already on a machine.  I have to assume that here. There's other issues with doing this. Now, things like PSGaller  stuff, you try to publish these to the PowerShell Gallery. You're not telling the gallery  that you have a dependency on this other module. That's completely hidden from those.  You can leave the required in your manifest. I don't know if that'll work. Let's see if  I have time to test that. I think I had that already. Double check. Let's try to import  that. It doesn't work. Anyone have a clue why? I'm using module. I have it as the first  executable thing in my script. That's another requirement with using module. Let's go back  to the car. Dot-sourced it. Classes aren't exposed even with using module if they're  dot-sourced from the PSM1. They have to be in the PSM1. That sucks because I don't like  10,000-line PSM1 files. We've got to do what we've got to do. Let's take, let's just pull  this out of there and let's stick it in here. Let's try this again. Save, save, save. It  works. Now I can do new car, new Tesla. There you go. That sucks. It's problematic. I don't  have, I no longer have a hard dependency on the car module. It's dangerous for me to  publish this because someone else pulling it down is not going to get that dependency.  And again, it's the same core problem as the classes and using module. They need to be  in the PSM1. Not dot-sourced. So let's try this a different way. Let's try to create,  to have a hard dependency on the module with required modules and use a class in the PSM1.  Actually, that's what I did there. Do I have a hard dependency in required module? No. Sorry.  Put that back. Car, the car module is now correct. The class is in the PSM1. Let's take  this using module out and do this how we think it should work. Anyone know why this, when  you paste, is really slow? Anyone see that? Anyone have that too? Yeah. Yeah. Yeah. Yeah.  Doesn't work. So this is what we would think we would need to do is have the required modules.  Again, the core problem is that required modules is not running using module under the covers.  It's using import module and we already know that doesn't work. So no joy. Required modules  is not using module. Soft dependency on car module. So we already did that one. I believe.  Yes, we did. Yeah. This works. It's yuck though. Like I said, things like PowerShell  Galley, we won't know about this dependency. If you try to import it, you have to assume  that that person already has that dependent module on the box and that's not good. Everyone  clear on that? That's kind of a long set of scenarios there. I should say that anyone  not clear. So let's go, let's restart this just in case. So we have classes. Tesla and  car and I want to import this thing. Let's just dot source this and see if it works.  Is it going to work? No, of course it's not. Normally, you know, functions, PowerShell  functions, you can write them in any order. You know, the PowerShell is going to parse  the whole thing before it does anything with them. Not so with classes. Order matters.  So let's fix that. And there you go. That works. This is a real pain in the butt once  you have a fairly advanced module and lots of classes and you need the things depend  on each other and you need to order them such. I don't know. I know someone that I've worked  with on a way to kind of like recursively try to import classes and then eventually it'll  work. Yeah, just keep on going until you've run out of exceptions. And that actually does  work but that's, yeah. Yeah, or I told them I was like you could probably use like the  abstract syntax tree and parse this stuff and then do a bunch of magic on your own to  parse all these files, come up with a graph of things to import and then import them.  I don't even think you can get to the AST without like it tries to compile. Oh, does  it? So you can get to that AST. I haven't even tried it so, okay. I think we attempted  something like that. Nice. Okay, so don't do that. So this is not really a dragon. This  is really just an FYI with classes. You want to hide properties from a class. Maybe they're  internal to the class. You don't want people to muck with them. But hidden, so you do that  with this hidden keyword on a property. So we have a class. Let's create a new instance  of it. So we have our name property. It's public. We don't see our secret in our super  secret password Hunter 2. But we can always get to this if we really want to with force.  And we can see the secret property there. You can also see a bunch of other methods  that PowerShell has created for you on this class and get and set. And this is internally  how these properties are being set. So can I actually see that secret? One thing it  does not do is give you a tab completion on these. So if I do f. I hit tab, I should  tab expand this and see this property like you would normally expect. Hidden properties  don't do that. So you have to know what you're looking for and you can see it. And you can  also set it. Of course, it's not a locked property. So don't use this. If you don't  use this to store secrets that you're thinking that you're going to hide from people. If  people, you know, and this is the same in C sharp, you know, hidden properties are  not meant to hide things. They're meant to tell people not to use them. They're internal  to the class and their implementation details of the class are like private APIs. And you  run the risk of breaking things when you try to use internal things because it's on them  to change them. They give you public properties to use. Don't use the internal ones because  they're free to change them. So that's really the gist of hidden properties. Not the hide  secrets from you. Just to tell the developer or the user, this is stuff that you don't,  this is just inside baseball stuff. Don't use it. So not really a dragon, but, you know,  they're not private and even private in C sharp is not really private. You can still  get to them. It's just hidden and not very well. I'm going to do one on time. Okay. Verbose  and debug. This is not really also a dragon, but this is just another stuff that is more  of an FY about working with classes. I got this demo class from a fellow on Stack Overflow.  But this test kind of gives you a nice rundown of how verbose and debug work inside classes.  So let's import this. So we have a class and then two functions. So inside these functions,  we have a normal function. It's going to create a new instance of that class, run a method  on it. Pretty simple. And we have an advanced function with command of binding that gives  us verbose and debug and what if and all that kind of fun stuff. All the stuff we should  be using for every single function we write, right? Right. I don't see any reason not to  use advanced functions personally. They just give you a lot of stuff for free. So that  works just like we would expect it. We create a new instance, pass it a property. Let's  try the advanced verbose works like we would expect it to. You can see my verbose messages  in my constructor and in my method. Debug should work the same way. It appears to. It ran  my debug message right there. So that's cool. But there's always a but. Well, actually this  works. So we'll just mainly set our verbose preference in our session. So we're not going  to wrap it around a function. And we'll create a new instance. I don't have verbose turned  on. I shouldn't expect any output and that's correct. Let's turn it on and run that again.  We get our verbose message like we would expect. Does debug work? They worked in the function.  So we'll set it to inquire. This will also work with the, I should say not work with  continue. Where's my debug message? The debug is turned on. Verbose seemed to work. Not  debug. Did anyone know why? Or have I seen this? Because I don't know the answer. I  think this is a bug. So does verbose. They're both in separate streams. Well, I mean, we  do continue. It won't do that, right? I don't see it. So I think this is a bug. I'm just  curious if anyone else has seen this. I'll probably raise this. But to me, that should  work just like verbose. So that is a potential gotcha. If you want to manually work these  classes and you want to turn on debug, you got to wrap it in a function and that sucks  for debugging purposes. All right. So requires. How many people use a requires module? Okay.  Some people. I personally don't use it. I think there's some problems with it. This  is just yet another one of them. I'll show you why. So requires at the beginning. Let  me run this in my session. Make sure I'm all fresh. Requires also does not import the  module correctly. Using modules is the only way to do this. That sucks. So requires not  going to happen. Let's use that. Let's do that. That worked. I should dot source this.  And I got my instance. So using module, don't use it if you want to use work directly with  classes. It's not going to work. Same problem as one scenario for something like that. The  other problem with requires modules is that it tends to swallow exceptions if you're trying  to import a module and that module doesn't, it fails to import and is thrown errors. Your  script, you're not going to see the actual reason why that module didn't import. It's  hiding stuff from you. You can lead to some debugging pain if you try to use requires.  So requires doesn't import classes. So run me first again. Refresh. So I have two modules  here called fruit. Two versions of it. One and two. You don't do anything particularly  special. They have a couple classes in there about a fruit based class and an apple that  extends it. And then I got version two. The only real difference between these is I think  version two added calories as a property. Let's close that. Okay. So I should be able  to see them here. So I added that. I can see version one and two. That's great. I'll, using  module because we know that's the correct way to do these things. I'm going to import fruit.  But hey, I got two versions of it. Which one did I import? I imported version two the latest  like you would expect if you didn't specify a version and you have multiple versions on  the machine. It's going to import the latest. What if I want to import an older one? What's  going to happen? Do you think that's going to work? Of course not. Yeah, there's a theme  here, right? So I got that. I got that. So anyone know what module specifications are?  Where you can basically have a hash table of the name and the version and you can use  this with import module and just give it that hash table to import that specific one. Do  you think that would work? It does kind of. It imported the module correctly. Why is this  here? I said version two. Well, version that you think it actually loaded. Oh, well, sorry.  I wanted version one. Why is version two here? Strike that reverse it. I restarted my session,  didn't I? Did I? Okay. Why do I have two? Why did two get loaded? I don't know. I told  it one. No, I told it one. I want one. The module specification is giving you a specific  version to import. No, it'll load the latest always. Yeah. So that's not cool. Why the  latest version? I think this is also a bug. I got to do some more investigation, try to  figure out some other edge cases to see why this is working this way. But module specifications  don't work or don't appear to work anyway. Not the way you would think. So let's look  at a more complex module. Fittingly named complex module. This is when I read modules.  This is how my common structure and probably most of it, a lot of people here is public  and private. Maybe classes if you have one, you got your manifest and PSM one and PSM  one is dot source of stuff. Or maybe it does a couple initialization stuff. So I have a  build script that just kicks off Saki. Anyone not know what Saki is? Okay. It's a build,  it's a task framework, kind of like make, rake, bake. I think there's a bake. There's  one of these for every language. They always sound like ache except for Saki because PSA  is hard sounding. Saki sounds cooler. But it's a build automation framework where you  can set up tasks and dependencies. So you have to, you know, usually for compiling code.  I use it for other things as well. As a side, I am, as of about six months ago, I'm now  the maintainer of Saki. It's gone through a couple maintainers over the years. I'm the  current red pirate Roberts for now anyway. And if you're interested in Saki, you can  hit me up after this. But I have a build script that this is going to run Saki and I can look  at my Saki task. I'm not going to go over too much of this, but basically I have a task  to clean my output folder, just wipe everything. I have a task to compile the module. You know,  we're not really compiling stuff in PowerShell, but really what I'm doing here is just gather  in up all these source files and just shoving them into a PSM1, just concatenate them all.  Because we know, we saw from other scenarios, classes need to be in the PSM1. I hate having  the giant PSM1. I want to keep everything in source separated. So when I build my module,  I just load up everything and put it in the PSM1 at build time.  The other caveat to this, so I should say, these are my classes. We have a planet-based  class. We have Mars and Earth. They inherit from planet. We know that order matters. So  the order in which you concatenate them in the PSM1 also matters. And this is pretty  simple with three classes, but my POSPOT module, which I'll talk about on Thursday, has like  30 different dependencies on different ones. And initially, what I was thinking was, oh,  well, I'll just order them 01, 02, 03, and I'll just sort them and concatenate them in  order. And I was like, oh, my God, this is just basic and go to 10 and 20 and 30, and  I want to insert a 15. And what if I have 01 and 02, and then I need to put one in the  middle, 01.5? And I was like, well, this is just stupid.  So I quickly decided not to depend on the file names to order these. I'm just brute-forcing  it. I just defined them in the order that I know that they're going to import. It works,  you know, for my case. But we have a module, sorry, an array of files. We're going to concatenate  them and just add them to a PSM1 file. So we do the same thing with our private and public  functions. The order of these don't matter, just like normal PowerShell. So let's see  what this does. So it created a bin folder, the version of the module I'm working with.  I got my manifest, and I got my nice big PSM1. In my possible model, this is like 8,000  lines. So this is my way of solving some of these problems with modules. Some of it's  just brute-force. Yeah.  That's kind of like the problem he was talking about. My solution or potential solution was  maybe looking at AST and inspecting these files to figure out what depends on what and  create a dependency graph of the order dynamically. That sounds great. Yeah. It works. Yeah. A  lot of the stuff is it works. Yeah.  Yeah. Yeah. They just suck. Yeah. I have a thanks for that. I've looked at some of  the PowerShell code for the using, and I know that parse time thing. I didn't know the technical  reasons on why it's different and why classes aren't exposed. I haven't got a good explanation  about why that's the case and why using modules needed in the first place. Yeah. Some custom  loader. Ideally, this stuff is a hand of the PowerShell itself. That's really the end.  This is really just trying to highlight issues that I find with these. I already know the  classes are not 100 percent. They got added to five. I don't think they've really changed  in six. I don't know what the roadmap is on classes and what they're going to fix.  You could potentially use a getPike data to see if the pipe exists in your built script  and if it doesn't check to see if there's a file that contains it.  I got a few more minutes here. That's my compiled model. That's how I do it. I just mainly create  a big monolithic PSM1. I ship it. But the same problem is with using module apply. In  that case, you have to use using module if you want to get access to those raw classes  directly. These two are a little extra. Let's see if I had time and I do. Classes. Anyone  know what an abstract class is in PowerShell? I should say in C-Sharp. The gist of it is  that, let me see if I got an example right here. I'm not a developer. This is my layman's  term. An abstract class is not a thing that you actually create. You don't create an  instance of an animal. That doesn't make any sense. You create an instance of a cat and  a dog. Those are subtypes of an animal. It makes no logical sense to create an animal.  That's what an abstract class is. It's a lot of base functionality that you would then  share in subsequent classes, but it makes no logical sense to create that base class.  Things wouldn't inherit from it. That's my layman's term for what an abstract class is.  Sorry, this PowerShell doesn't support them. You can create a janky version of it. Let's  create an animal class. We have an animal class. Here is a way to, and this is a runtime thing.  This is not a compile time thing. I say, hey, if I try to ever try to create an instance  of an animal, don't allow me. I'm going to throw an error. When the animal class gets  instantiated and this constructor gets run, I'm going to find out what my current type  is and I say, well, if it's animal, don't do this. This is mimicking a little bit of  the behavior of an abstract class. In C-sharp, this would be a compile time error if you  try to create an instance of it. This is a runtime check, though. This created another  class that inherits from animal. We can obviously create a, well, let's try to create an animal.  No, we can't. My error says, no, animals invalid. You have to inherit. Can I create  a cow? Yes, I can. Can I make it move? Yes, I can. That's a way to mimic abstract classes.  This is, I don't know if there's a way to do that in PowerShell. You can mimic them  sort of if you want to do this. It's not strictly needed. Again, abstract classes are things  that you don't actually create or would ever want to create an instance of it themselves.  Here's a little bit more about the internals of classes. I have this MyClass and I want  to pass it a value. In normal PowerShell and command list and functions, you do parameter  validation for string length and matching this regex or something like that. How do  you do that on a class method? You can't put parameter validation on this parameter that  you're passing in. Here's an, I'm not necessarily advocating for this, but this is something  you can play with. What this does is rely on some magic PowerShell stuff. Here's the  link to where I found this. Let's create a new instance. I got a little homegrown parameter  validation here. Why is that working? PowerShell is attempting to cast the string that I passed  in Brandon or BR to make a new instance of validated name. When the default valid, the  constructor of validated name gets created, at that point, it's using the normal parameter  validation to set the value of that property. This is relying on PowerShell's casting from  one type to another to do a little janky parameter validation directly on classes. I'm not necessarily  advocating the way they do this. I would just wrap them in normal PowerShell functions and  just do it the normal way. This is something I just saw that was interesting out there.  It doesn't always work though. This is a string. This is a valid string. I'm passing string.  I expect this thing to be a string. I passed it in integer. Well, integers can get cast  to strings when it needs to. PowerShell is going to want to do what it wants to do to  help you out. It thinks it's being nice and it's not. If I pass this string, it'll just  work. It should just work the same. Oh, like this. Let's see. Yeah, this is the same thing.  Yeah. No joy. We can't do it. We can add nice things. I don't think so. Yeah. Yeah, we're  wrapping up right now. Okay. Okay. This is some of the links that I have, some of the  projects that I'm working on. You can see Saki, Poshpot, which I'll talk about later,  OVF, Watchman, my all-give repos. It's not? Okay, I'll look at that. Thanks. Thank you.  Thank you.
Developing_with_PowerShell_Classes_0_47.png Developing_with_PowerShell_Classes_48_95.png Developing_with_PowerShell_Classes_96_143.png Developing_with_PowerShell_Classes_144_227.png Developing_with_PowerShell_Classes_228_1187.png Developing_with_PowerShell_Classes_1188_1607.png Developing_with_PowerShell_Classes_1608_2951.png Developing_with_PowerShell_Classes_2952_4751.png Developing_with_PowerShell_Classes_4752_6851.png Developing_with_PowerShell_Classes_6852_6899.png Developing_with_PowerShell_Classes_6900_8135.png Developing_with_PowerShell_Classes_8136_11447.png Developing_with_PowerShell_Classes_11448_12023.png Developing_with_PowerShell_Classes_12024_13115.png Developing_with_PowerShell_Classes_13116_13163.png Developing_with_PowerShell_Classes_13164_13523.png Developing_with_PowerShell_Classes_13524_13787.png Developing_with_PowerShell_Classes_13788_15827.png Developing_with_PowerShell_Classes_15828_18059.png Developing_with_PowerShell_Classes_18060_18491.png Developing_with_PowerShell_Classes_18492_18551.png Developing_with_PowerShell_Classes_18552_18827.png Developing_with_PowerShell_Classes_18828_18959.png Developing_with_PowerShell_Classes_18960_20111.png Developing_with_PowerShell_Classes_20112_20591.png Developing_with_PowerShell_Classes_20592_21287.png Developing_with_PowerShell_Classes_21288_21503.png Developing_with_PowerShell_Classes_21504_22799.png Developing_with_PowerShell_Classes_22800_22919.png Developing_with_PowerShell_Classes_22920_23087.png Developing_with_PowerShell_Classes_23088_25247.png Developing_with_PowerShell_Classes_25248_25295.png Developing_with_PowerShell_Classes_25296_37823.png Developing_with_PowerShell_Classes_37824_38591.png Developing_with_PowerShell_Classes_38592_40763.png Developing_with_PowerShell_Classes_40764_41603.png Developing_with_PowerShell_Classes_41604_41771.png Developing_with_PowerShell_Classes_41772_42203.png Developing_with_PowerShell_Classes_42204_42455.png Developing_with_PowerShell_Classes_42456_44303.png Developing_with_PowerShell_Classes_44304_45983.png Developing_with_PowerShell_Classes_45984_46367.png Developing_with_PowerShell_Classes_46368_46583.png Developing_with_PowerShell_Classes_46584_48035.png Developing_with_PowerShell_Classes_48036_49139.png Developing_with_PowerShell_Classes_49140_49355.png Developing_with_PowerShell_Classes_49356_49439.png Developing_with_PowerShell_Classes_49440_50975.png Developing_with_PowerShell_Classes_50976_52019.png Developing_with_PowerShell_Classes_52020_52127.png Developing_with_PowerShell_Classes_52128_55847.png Developing_with_PowerShell_Classes_55848_58595.png Developing_with_PowerShell_Classes_58596_59951.png Developing_with_PowerShell_Classes_59952_60575.png Developing_with_PowerShell_Classes_60576_64343.png Developing_with_PowerShell_Classes_64344_65639.png Developing_with_PowerShell_Classes_65640_65855.png Developing_with_PowerShell_Classes_65856_66203.png Developing_with_PowerShell_Classes_66204_66803.png Developing_with_PowerShell_Classes_66804_66947.png Developing_with_PowerShell_Classes_66948_66995.png