4
votes

I've been diving into some of the more advanced features of powershell modules and manifests recently, with a view to handling scenarios more advanced than just a basic export of a few functions. It sounds like it should be obvious, but I'm struggling to find a nice solution for sharing common 'helper' type functions across several large non trivial modules. In particular, I'm looking for a solution that:

  • Allows sharing of 'helper' type functions without necessarily being exported by anyone
  • Allow installation via PsGet from a local repo path

Let me go into some of the challenges I see.

First of all, as far as I can tell, PsGet does not handle module dependencies well. This implies sharing between modules is going to be a struggle. Maybe a solution to this is to avoid PsGet, and use a custom script to 'install' modules to the local module path, which might be more tolerant of dependencies and load order.

My point about not using module exports to share helper functions also seems to be an issue. The reason I can see for this is desiring aliases, helpers etc for common internal actions (needed inside useful functions), that are either useless or unsafe to expose. For example, a nice brief alias for getting the local script path (commonly used, noisier than it should be). Or I recently made a nice simple wrapper around PromptForChoice with fewer options. Maybe this whole thing isn't a real issue. But I can't help but feel that shipping a 'utils' module that exports low level functions that are useful inside real modules, but not to an end user, seems like the wrong way to go.

What I've been playing with is a small build structure that tests and then packs modules, and I want to get some code sharing possible. I've been looking for an alternative using ScriptsToProcess in the manifest, but these seem to be absolute paths, not relative.

Imagine a folder structure:

  • modules
    • utils
      • console_helpers.ps1
    • moduleA
      • moduleA.psm1
      • moduleA.psd1
    • moduleB
      • moduleB.psm1
      • moduleB.psd1
    • packed_modules
      • moduleA.zip
      • moduleB.zip

What I was considering was that you could list relative paths in each ScriptsToProcess, and then my pack phase will go and drag those relative paths in to each zip.

Is this a horrible crazy idea? Am I right that ps modules and PsGet really don't have decent dependency support? I would love to hear feedback from anyone who has looked into this space. I think the answer I'm hoping to get in rough priority might be:

  • Here's an example of sharing code without exposing it (probably a build/pack level solution)
  • Here's how to make module dependencies work nicely, using PsGet
  • Here's how to make module dependencies work nicely, but you can't use PsGet
  • Just expose everything from modules
  • This is a terrible idea and you're terrible

Thanks!

UPDATE as suggested by CalebB

Here's another example to illustrate what I'm trying to resolve. I find it useful to wrap up '&' style execution of commands with a wrapper function, to deal with stuff like checking exit codes etc. If i'm building half a dozen modules, many of them will want to make use of that helper (obviously).

My options today seem to be put it in a module and export it, but maybe I don't want it exported, I want more of a . source style access. And if I've got a family of modules all trying to use this stuff, the options for module dependency management are limited (PsGet limitation etc).

If I'm 'building' all the modules at once (with some decent psake and pester infrastructure), maybe I can use a hack at this point to embed scripts into my zipped modules to 'solve' all these problems?

2
For starters, your question is not terrible and is organized relatively well. However, this is rather subjective and your question is rather vague. Clarifying a more exact question and desired outcome behavior would be a better option for communicating your issue. :)CalebB

2 Answers

1
votes

Allows sharing of 'helper' type functions without necessarily being exported by anyone

Mhm... what is wrong with dot sourcing the scripts you need within particular module ? You could :

  • Keep your folder structure and symlink the desired functions into module folder.
  • Try to use AbsolutePath with ScriptProcess that has "relative part" in it, for example %PSScriptRoot%\..\utils (not tried in that context but generally works). If not, u can always add preprocessor to fix paths for you if it doesn't work
  • Delete undesired imported elements manually via function:, alias:, and var: provider.
  • Import extra utilities only when u use them then remove them at the end ? If the desire is that user can't see them you can encrypt them.

Here's how to make module dependencies work nicely, but you can't use PsGet

Chocolatey uses NuGet so it handles dependencies and can load from the local store. As a benefit, OneGet supports it which is something everybody will use eventually.

0
votes

I've posted the solution I've come up with on github. I've rolled in a few other features I want when building modules, but the key solution for this question here uses reading and updating the psd1 of each module.

You include scripts that you want to embed in the NestedModules property of your manifest. My build phase will find each script and copy it into the module folder for packing and zipping. The manifest that ships in the package has the script paths converted to the now local file name. I'm still not sure of this is ideal, but it seems to be a nice compromise to deal with the issues here.

A key issue I encountered along the way was that the ScriptsToProcess list is executed literally at module import time, so it is only useful for bootstrapping the import of your functionality. The NestedModules property is actually the list of additional scripts you want to be . sourced and available when your module is used.