How to export a class in a PowerShell v5 module - powershell

I've got a module setup to be like a library for a few other scripts. I can't figure out how to get a class declaration into the script scope calling Import-Module. I tried to arrange Export-Module with a -class argument, like the -function, but there isn't a -class available. Do I just have to declare the class in every script?
The setup:
holidays.psm1 in ~\documents\windows\powershell\modules\holidays\
active script calls import-module holidays
there is another function in holidays.psm1 that returns a class object correctly, but I don't know how to create new members of the class from the active script after importing
Here is what the class looks like:
Class data_block
{
$array
$rows
$cols
data_block($a, $r, $c)
{
$this.array = $a
$this.rows = $r
$this.cols = $c
}
}

PSA: There is a known issue that keeps old copies of classes in memory. It makes working with classes really confusing if you don't know about it. You can read about it here.
using is Prone to Pitfalls
The using keyword is prone to various pitfalls as follows:
The using statement does not work for modules not in PSModulePath unless you specify the module's full path in the using statement. This is rather surprising because although a module is available via Get-Module the using statement may not work depending on how the module was loaded.
The using statement can only be used at the very beginning of a "script". No combination of [scriptblock]::Create() or New-Module seems overcome this. A string passed to Invoke-Expression seems to act as a sort of standalone script; a using statement at the beginning of such a string sort of works. That is, Invoke-Expression "using module $path" can succeed but the scope into which the contents of the module are made available seems rather inscrutable. For example, if Invoke-Expression "using module $path" is used inside a Pester scriptblock, the classes inside the module are not available from the same Pester scriptblock.
The above statements are based on this set of tests.
ScriptsToProcess Prevents Access to Private Module Functions
Defining a class in a script referred to by the module manifest's ScriptsToProcess seems at first glance to export the class from the module. However, instead of exporting the class, it "creates the class in the global SessionState instead of the module's, so it...can't access private functions". As far as I can tell, using ScriptsToProcess is like defining the class outside the module in the following manner:
# this is like defining c in class.ps1 and referring to it in ScriptsToProcess
class c {
[string] priv () { return priv }
[string] pub () { return pub }
}
# this is like defining priv and pub in module.psm1 and referring to it in RootModule
New-Module {
function priv { 'private function' }
function pub { 'public function' }
Export-ModuleMember 'pub'
} | Import-Module
[c]::new().pub() # succeeds
[c]::new().priv() # fails
Invoking this results in
public function
priv : The term 'priv' is not recognized ...
+ [string] priv () { return priv } ...
The module function priv is inaccessible from the class even though priv is called from a class that was defined when that module was imported. This might be what you want, but I haven't found a use for it because I have found that class methods usually need access to some function in the module that I want to keep private.
.NewBoundScriptBlock() Seems to Work Reliably
Invoking a scriptblock bound to the module containing the class seems to work reliably to export instances of a class and does not suffer from the pitfalls that using does. Consider this module which contains a class and has been imported:
New-Module 'ModuleName' { class c {$p = 'some value'} } |
Import-Module
Invoking [c]::new() inside a scriptblock bound to the module produces an object of type [c]:
PS C:\> $c = & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
PS C:\> $c.p
some value
Idiomatic Alternative to .NewBoundScriptBlock()
It seems that there is a shorter, idiomatic alternative to .NewBoundScriptBlock(). The following two lines each invoke the scriptblock in the session state of the module output by Get-Module:
& (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
& (Get-Module 'ModuleName') {[c]::new()}}
The latter has the advantage that it will yield flow of control to the pipeline mid-scriptblock when an object is written to the pipeline. .NewBoundScriptBlock() on the other hand collects all objects written to the pipeline and only yields once execution of the entire scriptblock has completed.

I found a way to load the classes without the need of "using module".
In your MyModule.psd1 file use the line:
ScriptsToProcess = #('Class.ps1')
And then put your classes in the Class.ps1 file:
class MyClass {}
Update: Although you don't have to use "using module MyModule" with this method you still have to either:
Run "using module MyModule"
Or run "Import-Module MyModule"
Or call any function in your module (so it will auto import your module on the way)
Update2: This will load the Class to the current scope so if you import the Module from within a function for example the Class will not be accessible outside of the function. Sadly the only reliable method I see is to write your Class in C# and load it with Add-Type -Language CSharp -TypeDefinition 'MyClass...'.

According to here and here, you can use classes defined in your module by doing the following in PowerShell 5:
using module holidays

The using statement is the way to go if it works for you. Otherwise this seems to work as well.
File testclass.psm1
Use a function to deliver the class
class abc{
$testprop = 'It Worked!'
[int]testMethod($num){return $num * 5}
}
function new-abc(){
return [abc]::new()
}
Export-ModuleMember -Function new-abc
File someScript.ps1
Import-Module path\to\testclass.psm1
$testclass = new-abc
$testclass.testProp # Returns 'It Worked!'
$testclass.testMethod(500) # Returns 2500
$testclass | gm
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
testMethod Method int testMethod(System.Object num)
ToString Method string ToString()
testprop Property System.Object testprop {get;set;}

You pretty much cannot. According to about_Classes help:
Class keyword
Defines a new class. This is a true .NET Framework type. Class members are public, but only public within the module scope. You can't refer to the type name as a string (for example, New-Object doesn't work), and in this release, you can't use a type literal (for example, [MyClass]) outside the script/module file in which the class is defined.
This means, if you want to get yourself a data_block instance or use functions that operate those classes, make a function, say, New-DataBlock and make it return a new data_block instance, which you can then use to get class methods and properties (likely including static ones).

This certainly does not work as expected.
The idea in PowerShell 5 is that you can define your class in a separate file with a .psm1 extension.
Then you can load the definition with the command (e.g.):
using module C:\classes\whatever\path\to\file.psm1
This must be the first line in your script (after comments).
What causes so much pain is that even if the class definitions are called from a script, the modules are loaded for the entire session. You can see this by running:
Get-Module
You will see the name of the file you loaded. No matter if you run the script again, it will not reload the class definitions! (It won't even read the psm1 file.) This causes much gnashing of teeth.
Sometimes - sometimes - you can run this command before running the script, which will reload the module with refreshed class definitions:
Remove-Module file
where file is the name without path or extension. However, to save your sanity I recommend restarting the PowerShell session. This is obviously cumbersome; Microsoft needs to clean this up somehow.

I've encountered multiple issues regarding PowerShell classes in v5 as well.
I've decided to use the following workaround for now, as this is perfectly compatible with .NET and PowerShell:
Add-Type -Language CSharp -TypeDefinition #"
namespace My.Custom.Namespace {
public class Example
{
public string Name { get; set; }
public System.Management.Automation.PSCredential Credential { get; set; }
// ...
}
}
"#
The benefit is that you don't need a custom assembly to add a type definition. You can add the class definition inline in your PowerShell scripts or modules.
The only downside is that you will need to create a new runtime to reload the class definition after is has been loaded for the first time (just like loading assemblies in a C#/.NET domain).

The way I've worked around this problem is to move your custom class definition into an empty .ps1 file with the same name (like you would in Java/C#), and then load it into both the module definition and your dependent code by dot sourcing. I know this isn't great, but to me it's better than having to maintain multiple definitions of the same class across multiple files...

To update class definitions while developing, select the code for the class and press F8 to run the selected code. It is not as clean as the -Force option on the Import-Module command.
Seeing as using Module doesn't have that option and Remove-Module is sporadic at best, this is the best way I have found to develop a class and see the results without having to close down the PowerShell ISE and start it up again.

A surprising & cumbersome limitation of using module appears to be that any classes to expose outside of a module MUST be in the module's psm1 file itself.
A class definition to expose outside the module cannot be 'dotsourced' into the psm1 file from a separate ps1 file in the module
...this is as per the docs since v5.1 (to at least 7.2):
The using module statement imports classes from the root module
(ModuleToProcess) of a script module or binary module. It does not
consistently import classes defined in nested modules or classes
defined in scripts that are dot-sourced into the module. Classes that
you want to be available to users outside of the module should be
defined in the root module.
So therefore, it seems the simplest options (as discussed in other answers) are:
If you only need to reference class instances outside of its defining module, create public functions to return class instances:
function Get-MyModulesClass { [MyModuleClass]::New() }
To reference a class type outside of the module (eg specifing a function argument's type), the class must have been defined directly in the psm1 file of the module, and this psm1 file must have been included in your external script via using module (eg using module <relativePathToModulePsm1File>).
...and of course what doesn't help when figuring all this out is that classes don't reload so you need start a new powershell session every time you make a change to the classes.
Example Module
/MyLibaryModule/MyPrivateClass.ps1:
class MyPrivateClass {
[void] Test(){ Write-Host "Accessed private class methods!"}
}
/MyLibaryModule/MyLibraryModule.psm1
class MyPublicClass {} # Exposed classes MUST be defined in this file
. $PSScriptRoot\MyPrivateClass.ps1
function Get-MyPrivateClassInstance { [MyPrivateClass]::new()}
/ExampleScript.ps1
using module .\MyLibraryModule\MyLibraryModule.psm1
[MyPublicClass]$myVar1 # Works
[MyPrivateClass]$myVar2 # Errors
Import-Module .\MyLibraryModule\MyLibraryModule.psm1
$object = Get-MyPrivateClassInstance
$object.GetType().Name
$object.Test() # works
Output
InvalidOperation:
Line |
5 | [MyPrivateClass]$myVar2 # Errors
| ~~~~~~~~~~~~~~
| Unable to find type [MyPrivateClass].
MyPrivateClass
Accessed private class methods!

Related

Creating a method to execute based off instance?

Is there such a thing as creating a method in a class to do something based off one of the populated fields? Kind of like a static method.
Just trying to create my own Ping() Method but, would like it to just use the ComputerName property already populated.
Class Device {
[string]$ComputerName
[string]$Status
[string]$Manufacturer
[string]$Model
[string]$SerialNumber
[Void]Ping($ComputerName) {
$Echo_Reply = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet
$this.Status = $Echo_Reply
}
}
So if I instantiate it, assign a computer name to the property, can I use my Ping() method just as is without referring to it, to actually ping?
$Device = [device]::new()
$Device.ComputerName = 'ComputerOne'
Could I just use my method to ping it: $Device.Ping(), instead of referencing it $Device.Ping('ComputerOne').
You're looking for an instance method, not a static one, i.e. one that can implicitly operate on the specific state of an instance of the class at hand.
What the code in your question defines is an instance method, and, as mclayton points out, you must use $this.<property-name> to refer to an instance's properties from inside an instance method, so your method can be redefined to:
[void] Ping() {
$this.Status = Test-Connection -ComputerName $this.ComputerName -Count 1 -Quiet
}
Pitfall, as of PowerShell Core 7.2.0-preview.7:
If, after defining your class directly in your script, you try to redefine it later in the same script via dot-sourcing (. <script>), the redefinition is quietly ignored.
Arguably, such a redefinition in what is effectively the same scope should be prevented altogether, as is already the case if you try to redefine the same class directly in a given scope.
See GitHub issue #8767
When in doubt, start a new session to ensure that the latest definition of your class takes effect.

Powershell Factory Pattern as static method

I am looking to implement a tool that can manage files with a variety of locations; local files, UNC path files, files hosted on AWS, files hosted on a Synology NAS, etc. This will be done in PS 5.1 using classes and a number of specific patterns.
My thinking is I need a base path type, that has a property for the path itself. Then I need classes for UNC paths, local paths, AWS paths etc. that inherit form the base Path class. Then I can have a Copy class that handles the copy differently depending on the method signature. So UNC > UNC is handled differently from AWS > UNC. So, Strategy Pattern.
As for the path types, this seems like a place for the Factory Pattern. And every single example I have found on the internet for the Factory Pattern, in PowerShell, involves a dedicated Factory class. But it seems to me that logically, the Factory to create Path derivatives could/should be part of the Path class itself, as a static method. I searched on static factory method, and the concept is well documented for other languages, but not PowerShell. So I decided to just try it. To that end, I have this as a proof of concept...
class pxPath {
hidden $_path
pxPath () {}
pxPath ([string]$path) {
$this._path = $path
}
static [pxPath] Factory ([String]$path) {
[pxPath]$newPath = [uncFileSystem]::new($path)
return $newPath
}
}
class uncFileSystem : pxPath {
uncFileSystem ([String]$path) : base ([String]$path) {
}
}
$testPath = [pxPath]::Factory('\\Server\folder\file.txt')
$testPath.getType().FullName
$testPath._path
It doesn't have the multiple derived types needed for the Strategy Pattern, it just creates the one derived type. But it proves that a Factory as static method works.
My question is, is there some argument against this that I am not seeing, that explains why I have never seen an example like this for PowerShell? It makes SO much sense to me, and yet I have never seen it done, which really gives me pause.

Alternative to Global Variable or Singleton

I have read in various places that Global variables are at best a code smell, and best avoided. At the moment I am working on refactoring a big function based PS script to classes, and thought to use a Singleton. The use case being a large data structure that will need to be referenced from a lot of different classes and modules.
Then I found this, which seems to suggest that Singletons are a bad idea too.
So, what IS the right way (in PS 5.1) to create a single data structure that needs to be referenced by a lot of classes, and modified by some of them? Likely pertinent is the fact that I do NOT need this to be thread safe. By definition the queue will be processed in a very linear fashion.
FWIW, I got to the referenced link looking for information on singletons and inheritance, since my singleton is simply one of a number of classes with very similar behavior, where I start with the singleton which contains collections of the next class, which each contain collections of the next class, to create a hierarchical queue. I wanted to have a base class that handled all the common queue management then extend that for the differing functionality lof each class. Which works great other than having that first extended class be a singleton. That seems to be impossible, correct?
EDIT: Alternatively, is it possible with this nested classes in a generic list property approach to be able to identify the parent from within a child? This is how I handled this is the Function based version. A global [XML] variable formed the data structure, and I could step through that structure, using .SelectNode() to populate a variable to pass to the next function down, and using .Parent to get information from higher up, and especially from the root of the data structure.
EDIT: Since I seem not to be able to paste code here right now, I have some code on GitHub. The example here of where the Singleton comes in is at line 121, where I need to verify if there are any other examples of the same task that have not yet comnepelted, so I can skip all but the last instance. This is a proof of concept for deleting common components of various Autodesk software, which is managed in a very ad hoc manner. So I want to be able to install any mix of programs (packages) and uninstall on any schedule, and ensure that the last package that has a shared component uninstall is the one that uninstalls it. So as to no break other dependent programs before that last uninstall happens. Hopefully that makes sense. Autodesk installs are a fustercluck of misery. If you don't have to deal with them, consider yourself lucky. :)
To complement Mathias R. Jessen's helpful answer - which may well be the best solution to your problem - with an answer to your original question:
So, what IS the right way (in PS 5.1) to create a single data structure that needs to be referenced by a lot of classes, and modified by some of them [without concern for thread safety]?
The main reason that global variables are to be avoided is that they are session-global, meaning that code that executes after your own sees those variables too, which can have side effects.
You cannot implement a true singleton in PowerShell, because PowerShell classes do not support access modifiers; notably, you cannot make a constructor private (non-public), you can only "hide" it with the hidden keyword, which merely makes it less discoverable while still being accessible.
You can approximate a singleton with the following technique, which itself emulates a static class (which PowerShell also doesn't support, because the static keyword is only supported on class members, not the class as a whole).
A simple example:
# NOT thread-safe
class AlmostAStaticClass {
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static [string] $Message # static property
static [string] DoSomething() { return ([AlmostAStaticClass]::Message + '!') }
}
[AlmostAStaticClass]::<member> (e.g., [AlmostAStaticClass]::Message = 'hi') can now be used in the scope in which AlmostAStaticClass was defined and all descendant scopes (but it is not available globally, unless the defining scope happens to be the global one).
If you need access to the class across module boundaries, you can pass it as a parameter (as a type literal); note that you still need :: to access the (invariably static) members; e.g.,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])
Implementing a thread-safe quasi-singleton - perhaps for use
with ForEach-Object -Parallel (v7+) or Start-ThreadJob (v6+, but installable on v5.1) - requires more work:
Note:
Methods are then required to get and set what are conceptually properties, because PowerShell doesn't support code-backed property getters and setters as of 7.0 (adding this ability is the subject of this GitHub feature request).
You still need an underlying property however, because PowerShell doesn't support fields; again the best you can do is to hide this property, but it is technically still accessible.
The following example uses System.Threading.Monitor (which C#'s lock statement is based on) to manage thread-safe access to a value; for managing concurrent adding and removing items from collections, use the thread-safe collection types from the System.Collections.Concurrent namespace.
# Thread-safe
class AlmostAStaticClass {
static hidden [string] $_message = '' # conceptually, a *field*
static hidden [object] $_syncObj = [object]::new() # sync object for [Threading.Monitor]
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static SetMessage([string] $text) {
Write-Verbose -vb $text
# Guard against concurrent access by multiple threads.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
[AlmostAStaticClass]::_message = $text
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
}
static [string] GetMessage() {
# Guard against concurrent access by multiple threads.
# NOTE: This only works with [string] values and instances of *value types*
# or returning an *element from a collection* that is
# only subject to concurrency in terms of *adding and removing*
# elements.
# For all other (reference) types - entire (non-concurrent)
# collections or individual objects whose properties are
# themselves subject to concurrent access, the *calling* code
# must perform the locking.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
$msg = [AlmostAStaticClass]::_message
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
return $msg
}
static [string] DoSomething() { return ([AlmostAStaticClass]::GetMessage() + '!') }
}
Note that, similar to crossing module boundaries, using threads too requires passing the class as a type object to other threads, which, however is more conveniently done with the $using: scope specifier; a simple (contrived) example:
# !! BROKEN AS OF v7.0
$class = [AlmostAStaticClass]
1..10 | ForEach-Object -Parallel { ($using:class)::SetMessage($_) }
Note: This cross-thread use is actually broken as of v7.0, due to classes currently being tied to the defining runspace - see this GitHub issue. It is to be seen if a solution will be provided.
As you can see, the limitations of PowerShell classes make implementing such scenarios cumbersome; using Add-Type with ad hoc-compiled C# code is worth considering as an alternative.
This GitHub meta issue is a compilation of various issues relating to PowerShell classes; while they may eventually get resolved, it is unlikely that PowerShell's classes will ever reach feature parity with C#; after all, OOP is not the focus of PowerShell's scripting language (except with respect to using preexisting objects).
As mentioned in the comments, nothing in the code you linked to requires a singleton.
If you want to retain a parent-child relationship between your ProcessQueue and related Task instance, that can be solved structurally.
Simply require injection of a ProcessQueue instance in the Task constructor:
class ProcessQueue
{
hidden [System.Collections.Generic.List[object]]$Queue = [System.Collections.Generic.List[object]]::New()
}
class Task
{
[ProcessQueue]$Parent
[string]$Id
Task([string]$id, [ProcessQueue]$parent)
{
$this.Parent = $parent
$this.Id = $id
}
}
When instantiating the object hierarchy:
$myQueue = [ProcessQueue]::new()
$myQueue.Add([Task]#{ Id = "id"; Parent = $myQueue})
... or refactor ProcessQueue.Add() to take care of constructing the task:
class ProcessQueue
{
[Task] Add([string]$Id){
$newTask = [Task]::new($Id,$this)
$Queue.Add($newTask)
return $newTask
}
}
At which point you just use ProcessQueue.Add() as a proxy for the [Task] constructor:
$newTask = $myQueue.Add($id)
$newTask.DisplayName = "Display name goes here"
Next time you need to search related tasks from a single Task instance, you just do:
$relatedTasks = $task.Parent.Find($whatever)

Can I #define a constant solutionwide within c# code without project settings?

I know this was aksed and answered a a couple of times e.g.
Solution-wide #define, Is There anyway to #define Constant on a Solution Basis? and How to define a constant globally in C# (like DEBUG).
But in my case I can not use any of the suggested methods:
I'm writing on different "modules" (or plugins if you want so) for UnityProjects (kind of a package providing a certain functionality). The idea is that a developer can load a certain "module" to use in his project by importing a UnityPackage with all scripts and resources in it.
But some of these modules themselves depend on other modules. So what I tried so far was having a class Constants in each module with seperated namespaces and preprocessor definitions.
Module A
#if !MODULE_A
#define MODULE_A // BUT I WOULD NEED THIS GLOBAL NOT ONLY HERE
#endif
namespace Module_A
{
public static class Constants
{
// some constants for this namespace here
}
}
Module B
#if !MODULE_B
#define MODULE_B // BUT I WOULD NEED THIS GLOBAL NOT ONLY HERE
#endif
#if !MODULE_A // WILL BE NOT DEFINED OFCOURSE SINCE #define IS NOT GLOBAL
#error Module A missing!
#else
namespace Module_B
{
public static class Constants
{
// some constants for this namespace here
}
// and other code that might require Module A
}
#endif
But ofcourse this cannot work like this since #defines are not global but only in the current file.
Problem
For this whole idea of modules and a simple "load your modules" I can not ask the user to first make changes to the project or solution settings how e.g. suggested by this answer but instead have to use only the (c#) resources that come imported with the UnityPackage (at least with my current know-how).
Is there any way to somehow set/define those constants for the entire Unity-Project by only importing the module's UnityPackage?
Edit:
I could find a solution for 1 definition in Unity using Assets/msc.rsp. But this still wouldn't work for multiple modules since they would have to write into the same file.
After a lot of searches I've finally been able to put together a surprisingly simple solution I'ld like to share with you:
InitializeOnLoad
Unity has an attribute [InitializeOnLoad]. It tells Unity to initialize according class as soon as
Unity is launched
After any re-compiling of scripts => also after importing a new unitypackage with scripts
static Constructor
In their Running Editor Code On Launch example, they show, how to combine this with a static constructor.
From static-constructors:
A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.
While usually you still would have to create an instance of the class, the static constructor is "instanciated/executed" instantly when the class is initliazed, which we force using the [InitializeOnLoad] attribute.
Scripting Define Symbols
Further Unity actually has project wide defines in the PlayerSettings.
And the good part is: We also have access to them via scripting API:
PlayerSettings.GetScriptingDefineSymbolsForGroup
PlayerSettings.SetScriptingDefineSymbolsForGroup.
So what I did now is the following
Module A
This module has no dependencies but just defines a "global define" in the PlayerSettings. I placed this script somewhere e.g. in Assets/ModuleA/Editor (important is the last folder's name).
using System.Linq;
using UnityEditor;
namespace ModuleA
{
// Will be initialized on load or recompiling
[InitializeOnLoad]
public static class Startup
{
// static constructor is called as soon as class is initialized
static Startup()
{
#region Add Compiler Define
// Get the current defines
// returns a string like "DEFINE_1;DEFINE_2;DEFINE_3"
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
// split into list just to check if my define is already there
var define = defines.Split(';').ToList();
if (!define.Contains("MODULE_A")
{
// if not there already add my define
defines += ";MODULE_A";
}
// and write back the new defines
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines);
#endregion
}
}
}
Module B
This module depends on Module A. So itself defines a "global define" (so later Modules can check their dependecies on Module B) but additionally it checks first, if Module A is imported. If Module A is missing, it prints an error to the Debug Console.
(You could as well throw a compiler error using #error SOME TEXT, but for some reason this is not capable of printing out the URL correctly so I decided for the Debug.LogError)
I placed this script somewhere e.g. in Assets/ModuleB/Editor
#if MODULE_A
using System.Linq;
#endif
using UnityEditor;
#if !MODULE_A
using UnityEngine;
#endif
namespace ModuleB
{
// Will be initialized on load or recompiling
[InitializeOnLoad]
public static class Startup
{
// static constructor is called as soon as class is initialized
static Startup()
{
#if !MODULE_A
Debug.LogErrorFormat("! Missing Module Dependency !" +
"\nThe module {0} depends on the module {1}." +
"\n\nDownload it from {2} \n",
"MODULE_B",
"MODULE_A",
"https://Some.page.where./to.find.it/MyModules/ModuleA.unitypackage"
);
#else
// Add Compiler Define
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var define = defines.Split(';').ToList();
if (!define.Contains("MODULE_B"))
{
defines += ";MODULE_B";
}
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines);
#endif
}
}
}
So later in other scripts of Module B I have two options (both do basically the same)
I can either check everywhere #if MODULE_A to check exactly the module this script relies on
or I can instead check #if MODULE_B to rather check with one line if all dependecies are fulfilled since otherwise I don't define MODULE_B.
On this way I can completely check all dependencies between certain modules which is awesome. The only two flaws I saw until now are:
We have to know how the define (e.g. MODULE_A) looks like for every module and if it is changed in the future it has to be changed in all depending modules as well
The "global define" isn't getting removed in case the module is deleted from the project
But well - which solution is perfect?
In general, the way I would solve this problem in C# is by defining a common set of interfaces that all your modules would contain. I think you can do this with Unity by placing the files from each module in the same location, thus allowing later installations to overwrite those same files (with, obviously, the same content). You would then put editor controls that expose properties to hold instances of those interfaces and then wire them up in the UI. You would test those properties for a value of null to determine which ones are missing.
Common.cs:
public interface IModuleA {}
public interface IModuleB {}
ModuleA.cs
public class ModuleA : IModuleA {}
ModuleB.cs
public class ModuleB : IModuleB
{
public IModuleA ModuleAInstance {get; set;}
private bool IsModuleAPresent()
{
return !ModuleAInstance == null;
}
}
The ideal way to solve it would be with a package manager and proper dependency injection, but doing that with Unity is not straightforward.

How to instantiate an object in Powershell which must derive from a .Net abstract class

I want to use Powershell to insert entities into Azure Table Storage and therefore need to instantiate a class which derives from the .NET abstract class Microsoft.WindowsAzure.StorageClient.TableServiceEntity
I am thinking to instantiate (in a Powershell 2.0 script) an object using eg New-Object and then adding members using Add-Member but cannot achieve the instantiation of a compatible TableServiceEntity derived class. (I can instantiate other arbitrary PSObjects, but they won't do it seems)
Alternatively if someone could show me how to instantiate and populate ANY object class which could then be passed into the tableServiceContext.AddObject() method
What you can do is to create your class in a cs file and then reference it to your script.
Here's example:
Save this as a cs file:
using System.Collections;
public class MyList : System.Collections.Generic.List<string>
{
public static string Test(string input)
{
return string.Format("test input {0}", input);
}
}
And here's the code to run it:
cd c:\pst
Add-Type -Path "2.cs" #-ReferencedAssemblies $assembly
[MyList]::Test("aaa")
You might need to pass your assembly to ReferencedAssemblies . And you also might need to upload assemblies into your script
The way to create table is described here:
Add or replace entity in Azure Table Storage