Access Script Modules from via powershell

I am trying to save the script module from the library locally to the server I am running the tentacle on. However, I can’t see it in the octopus hashtable or through the client. Is this exposed in a way I can access it via powershell so I can save it on the server running the code?

Thanks

Hi Matthew,

Thanks for getting in touch.

Script Modules are available as variables during deployment on the Tentacle machines. The variables will have a name like: Octopus.Script.Module[Script Module Name]. I would recommend wrapping the whole Script Module in a Here-string with single quotes to avoid any escaping issues and to also avoid accidental variable interpolation. Try the following:

Write-Host @'
#{Octopus.Script.Module[Your Script Module Name]}
'@

If you want to access the Script Modules through the Octopus API or the Octopus Client, you can do so through the LibraryVariableSets endpoints. Script Modules are stored in Octopus as a Variable contained inside its own Library Variable Set, so there will be one Library Variable Set for each Script Module.

You can even add a query string to only return the Library Variable Sets for Script Modules and exclude any other Library Variable Sets. Just perform a GET request against this endpoint: api/libraryvariablesets?contentType=ScriptModule.

Hope this helps! Let me know if you have any other questions.
Tom

I too am trying to access the body of a scriptmodule using the LibraryVariableSets endpoint. Unfortunately, the object returned when i provide it the Id only contains the following properties: Id, Name, Description, VariableSetId, ContentType, Templates, Links. Is accessing Octopus.Script.Module[ModuleName] the only way to obtain the body?

I managed to get the Octopus Variable to save the module in a step. I need
to create the psd1 file but its heading in the right direction. Code so
far in case someone else can use it.

$suModulePath = 'C:\Program
Files\WindowsPowerShell\Modules\ScaleUnitTranslator’
if (test-path $suModulePath) {
write-verbose “scaleUnitTranslator path”
} else {
mkdir $suModulePath
}

$ScriptModule = @’
#{Octopus.Script.Module[Scale Unit Translator]}
’@

#write-verbose $ScriptModule

$suModuleFile = join-path $suModulePath 'ScaleUnitTranslator.psm1’
set-content -path $suModuleFile -value $ScriptModule -force

Thanks matthew. What you provided should work for my scenario. I was looking to inject (during the the post-deploy phase) a cookie-cutter windows service watchdog powershell script with a predictable name so it can be invoked by a scheduled task in Windows.

was trying to fork library and create a step template. If you’re
interested in trying it?


{
	"Id": "107096a4-a58d-4724-9ffa-3ddeb6edd1bd",
	"Name": "Save Script Module from Library",
	"Description": "Saves script module from library to local powershell module directory",
	"ActionType": "Octopus.Script",
	"Version": 0,
	"CommunityActionTemplateId": null,
	"Properties": {
		"Octopus.Action.Script.Syntax": "PowerShell",
		"Octopus.Action.Script.ScriptSource": "Inline",
		"Octopus.Action.RunOnServer": "false",
		"Octopus.Action.Script.ScriptBody": "$slmSaveModuleName = $slmModuleName.Replace(' ','')\n\n$slmModulePath = \"C:\\Program Files\\WindowsPowerShell\\Modules\\$slmSaveModuleName\\\"\nif (test-path $slmModulePath) {\n    write-verbose \"$slmModulePath path found\"\n} else {\n    mkdir $slmModulePath \n}\n\n$slmScriptModule = @\"\n#{Octopus.Script.Module[$slmModuleName]}\n\"@\n\n\n$slmModuleFile = join-path $slmModulePath \"$slmSaveModuleName.psm1\"\nset-content -path $slmModuleFile -value $slmScriptModule -force\n$slmPSDModuleFile = join-path $slmModulePath \"$slmSaveModuleName.psd1\"\nNew-ModuleManifest -RootModule $slmModuleFile -Path $slmPSDModuleFile \n\n\n\n",
		"Octopus.Action.Script.ScriptFileName": null,
		"Octopus.Action.Package.FeedId": null,
		"Octopus.Action.Package.PackageId": null
	},
	"Parameters": [{
		"Id": "cc771b5b-e76f-47bb-9983-0d98e8e122f4",
		"Name": "slmModuleName",
		"Type": "String",
		"Label": "Library Module Name",
		"HelpText": "Library Module Name",
		"DefaultValue": "",
		"DisplaySettings": {
			"Octopus.ControlType": "SingleLineText"
		},
		"Links": {}
	}],
	"LastModifiedOn": "2017-07-21T11:49:03.577+00:00",
	"LastModifiedBy": "lucidqdreams",
	"$Meta": {
		"ExportedAt": "2017-07-21T18:17:03.847Z",
		"OctopusVersion": "3.11.15",
		"Type": "ActionTemplate"
	},
	"Category": "PowerShell"
}

Hi Matthew and Tristan,

I’m glad you are having some success with saving the script module! It would be fantastic if you could submit a Pull Request at https://github.com/OctopusDeploy/Library when your step template is ready to go.

With regards to accessing the body of the script module using the API, it looks like you are accessing the collection of script modules from api/libraryvariablesets (or api/libraryvariablesets?contentType=ScriptModule). This will return a result set like:

{
    "ItemType": "LibraryVariableSet",
    "IsStale": false,
    "TotalResults": 1,
    "ItemsPerPage": 30,
    "Items": [
        {
            "Id": "LibraryVariableSets-1",
            "Name": "My Script Module",
            "Description": "Some description",
            "VariableSetId": "variableset-LibraryVariableSets-1",
            "ContentType": "ScriptModule",
            "Templates": [],
            "Links": {
                "Self": "/api/libraryvariablesets/LibraryVariableSets-1",
                "Variables": "/api/variables/variableset-LibraryVariableSets-1"
            }
        }
    ],
    "Links": {
        "Self": "/api/libraryvariablesets",
        "Template": "/api/libraryvariablesets{?skip,contentType,take,ids}",
        "Page.Current": "/api/libraryvariablesets?skip=0&take=30",
        "Page.0": "/api/libraryvariablesets?skip=0&take=30"
    }
}

As you mentioned, this contains fields like Id, Name and Description, but does not contain the body of the script module. To access the body of the script module, you will need to request the variables from the /api/variables endpoint. In the above JSON result, this means following the link contained in Item.Links.Variables. The script module is stored as a Variable Set that contains a single variable. The single variable represents the body of the script module.

So in this case, submitting a request to api/variables/variableset-LibraryVariableSets-1 will return a result like:

{
    "Id": "variableset-LibraryVariableSets-1",
    "OwnerId": "LibraryVariableSets-1",
    "Version": 3,
    "Variables": [
        {
            "Id": "03473d10-8a70-461c-81a7-fa4c7589edc2",
            "Name": "Octopus.Script.Module[My Script Module]",
            "Value": "function Say-Hello()\r\n{\r\n    Write-Output \"Hello, Octopus!\"\r\n}\r\n",
            "Scope": {},
            "IsEditable": true,
            "Prompt": null,
            "Type": "String",
            "IsSensitive": false
        }
    ],
    "ScopeValues": {...},
    "Links": {
        "Self": "/api/variables/variableset-LibraryVariableSets-1"
    }
}

Here you can see the body of the script module can be accessed through Variables[0].Value.

Feel free to ask if you have any other questions.

Tom

Hi Tom,
Thanks for the tip! Would you be able to share a method for replacing the escape characters added by Octopus when a script module is saved? For example, if I’m saving the module body in the example above to a file, I’d probably need to replace \t with tabs, \r\n with carriage return and new lines. I’m not quite sure what to do with backward slashes before a double quote.

Thanks.

Hi Tristan,

It all depends on how you handle the response from your request, and what language you are writing your code in. The Script Module body is included as part of a JSON web response, so the string is encoded according to standard JSON string encoding rules. The recommended way of handling this is to use a JSON conversion library that handles this for you.

If you are writing Powershell, you can do something like this:

$baseUri = "" # Add  your Octopus instance URI here

$apiKey = "API-" # Add your API key here

$headers = @{"X-Octopus-ApiKey" = $apiKey}

function Get-OctopusResource([string]$uri) {
    Write-Host "[GET]: $uri"
    return Invoke-RestMethod -Method Get -Uri "$baseUri/$uri" -Headers $headers
}

$variables = Get-OctopusResource "api/variables/variableset-LibraryVariableSets-1"
Write-Host $variables.Variables[0].Value # This will be formatted correctly

In this case, Invoke-RestMethod will convert the JSON response into a Powershell object, and therefore handle the escaped characters for you. Alternatively, you could use ConvertFrom-Json to convert a raw JSON string into the same Powershell object.

If you were in C#, you could do something like this:

void Main()
{
	// This has the same character escaping as if you had obtained the string from the Octopus Variable API call
	string scriptModuleBody = @"function Say-Hello()\r\n{\r\n    Write-Output \""Hello, Octopus!\""\r\n}\r\n";
	
	string scriptModuleJson = $@"{{ Value: ""{scriptModuleBody}""}}";
	ScriptModule scriptModule = JsonConvert.DeserializeObject<ScriptModule>(scriptModuleJson);
	
	Console.WriteLine(scriptModule.Value); // Outputs the correctly formatted Script Module Body
}

class ScriptModule
{
	public string Value { get; set; }
}

The method JsonConvert.DeserializeObject comes from the JSON.NET library, which will handle the escaped characters for you.

However, if you were writing C# or Powershell code, you would probably be better off using the Octopus Client which gives you a higher level abstraction around all Octopus resources and will handle everything for you.

Hope that helps!

Tom