Add Step Is Not Working

Hi Team,

I am trying to create to create a new project and add “script step” or “Run As Script” using Octopus REST API.

New Octopus Project gets created using REST API. However, which script is trying to add “script step” or “Run As Script” , it is throwing Error : Invoke-RestMethod : The remote server returned an error: (400) Bad Request.

I am putting script , please what I am missing:

PowerShell Script:

# Error Action Preference
$ErrorActionPreference = "Stop";

# Define Working Variables
$OctopusURL = ""
$OctopusAPIKey = ""
$Header = @{ "X-Octopus-ApiKey" = $OctopusAPIKey }
$SpaceName = "Default"
$OctopusProjectName = ""
$ProjectDescription = ""
$ProjectGroupName = ""
$LifecycleName = ""
$Role = ""

# Get Space
$Space = (Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/spaces/all" -Headers $Header) | Where-Object { $_.Name -eq $SpaceName }

# Get Project Group
$ProjectGroup = (Invoke-RestMethod -Method Get "$OctopusURL/api/$($space.Id)/projectgroups/all" -Headers $Header) | Where-Object { $_.Name -eq $($ProjectGroupName) }

# Get Lifecycle
$LifeCycle = (Invoke-RestMethod -Method Get "$OctopusURL/api/$($space.Id)/lifecycles/all" -Headers $Header) | Where-Object { $_.Name -eq $($LifecycleName) }
    
# Create Project
$TargetProjectName = ((Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/projects/all" -Headers $Header) | Where-Object { $_.Name -eq $OctopusProjectName })
    
# Construct Project Json Payload
$JsonPayload = @{
    Name           = $($OctopusProjectName)
    Description    = $($ProjectDescription)
    ProjectGroupId = $($ProjectGroup.Id)
    LifeCycleId    = $($LifeCycle.Id)
}

if ($($null) -eq ($($TargetProjectName).Name)) {
        
    try {
        Write-Host "Creating Project $($OctopusProjectName) ..." `n
        # Convert JsonPayload To Json
        $JsonPayload = $JsonPayload | ConvertTo-Json -Depth 10

        # Create Project
        Invoke-RestMethod -Method Post -Uri "$OctopusURL/api/$($Space.Id)/projects" -Body $JsonPayload -Headers $Header | Out-Null

        # Set Project Information After Creation
        $TargetProjectName = ((Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/projects/all" -Headers $Header) | Where-Object { $_.Name -eq $OctopusProjectName })

        Write-Host "Deleting & Re-Creating Project $($TargetProjectName.Name) Is Completed Now !!!" `n
    }
    catch {
        Write-Warning $_.Exception.Message
    }
}
else {
    Write-Host "Project $($TargetProjectName.Name) Is Already Exists" `n
        
    Write-Host "Deleting & Re-Creating Project $($TargetProjectName.Name) ..." `n

    # Set Project Information Before Deletion
    $TargetProjectName = ((Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/projects/all" -Headers $Header) | Where-Object { $_.Name -eq $OctopusProjectName })
                
    # Delete Project
    Invoke-RestMethod -Method Delete -Uri "$OctopusURL/api/$($Space.Id)/projects/$($TargetProjectName.Id)" -Headers $Header | Out-Null

    # Create Json PayLoad
    $JsonPayLoad = ($($JsonPayLoad) | ConvertTo-Json -Depth 10)

    # Create Project Using Cloning Method
    Invoke-RestMethod -Method Post -Uri "$OctopusURL/api/$($Space.Id)/projects?clone=$($ProjectId)" -Body $($JsonPayLoad) -Headers $Header | Out-Null

    # Set Project Information After Creation
    $TargetProjectName = ((Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/projects/all" -Headers $Header) | Where-Object { $_.Name -eq $OctopusProjectName })

    Write-Host "Deleting & Re-Creating Project $($TargetProjectName.Name) Is Completed Now !!!" `n
}    

##################################################################################################################################################

# Create Steps In Project

# Get Project
$OctopusProject = (Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/Projects/all" -Headers $Header) | Where-Object { $_.Name -eq $OctopusProjectName }

# Get Deployment Process
$DeploymentProcess = (Invoke-RestMethod -Method Get -Uri "$OctopusURL/api/$($Space.Id)/projects/$($OctopusProject.Id)/deploymentprocesses" -Headers $Header)

# Get Current Steps
$Steps = $DeploymentProcess.Steps

# Construct Notes String
switch ($OctopusProjectName) {
    OctopusProjectABC { 
        $StepNameNoteString = "XYZ"       
        $ScriptPath = ($($PSScriptRoot) + "\IIS-AppPool-Start.ps1")
        $ScritpContent = (Get-Content -Path $($ScriptPath) -Raw)
        $ScriptBody = @"
$($ScritpContent)
"@
    }    
}

$Steps += @{
    ID                 = ""
    Name               = "IISAppPoolStop"
    Slug               = "IISAppPoolStop"
    PackageRequirement = "LetOctopusDecide"
    Properties         = @{
        'Octopus.Action.TargetRoles' = $Role
    }
    Condition          = "Success"
    StartTrigger       = "StartAfterPrevious"    
    Actions            = @(
        @{          
            ID                            = ""
            Name                          = "IISAppPoolStop"
            Slug                          = "IISAppPoolStop"
            ActionType                    = "Octopus.Script"
            Notes                         = "**Stop $($StepNameNoteString) IIS AppPool**"
            IsDisabled                    = $false
            CanBeUsedForProjectVersioning = $true
            IsRequired                    = $false
            WorkerPoolId                  = ""            
            Container                     = @{
                "FeedId" = $null
                "Image"  = $null
            }
            WorkerPoolVariable            = ""                        
            Environments                  = @()
            ExcludedEnvironments          = @()
            Channels                      = @()
            TenantTags                    = @()            
            Packages                      = @()
            Condition                     = "Success"
            Properties                    = @{
                'Octopus.Action.RunOnServer'         = "false"
                'Octopus.Action.EnabledFeatures'     = ""
                'Octopus.Action.Script.ScriptSource' = "Inline"
                'Octopus.Action.Script.Syntax'       = "PowerShell"
                'Octopus.Action.Script.ScriptBody'   = $($ScriptBody)
            }                                          
        }
    )
}

# Convert Steps To Json
$DeploymentProcess.Steps = $Steps
$JsonPayload = $DeploymentProcess | ConvertTo-Json -Depth 10

# Submit Request
Invoke-RestMethod -Method Put -Uri "$OctopusURL/api/$($Space.Id)/projects/$($OctopusProject.Id)/deploymentprocesses" -Headers $Header -Body $JsonPayload | Out-Null

Thanks
Vivek

Hey there Vivek, thanks for reaching out. Are you using the self-hosted version of Octopus or are you using Octopus Cloud? If you’re using the self-hosted version, you should be able to see what the error is by viewing the Diagnostics tab. This will usually point out what the problem is. I’ll take a gander at the script to see if anything immediately pops out.

Regards,

Shawn

I copied your script and did some testing. I was able to get it to fail with a 400 error when the $ScriptBody variable was null or empty. Please check that variable to ensure it has something

Regards,

Shawn

Hi @Shawn_Sesna ,

Thanks for reply.

I have Powershell script which is “IIS-AppPool-Stop.ps1” in same directory along with the script which I have originally posted. If you wanted to test and try, I would request you to save both script and same directory and try to execute it through PowerShell ISE.

$ScriptBody variable is holding the content of script “IIS-AppPool-Stop.ps1” and the snippet for this is

# Construct Notes String
switch ($OctopusProjectName) {
    OctopusProjectABC { 
        $StepNameNoteString = "XYZ"       
        $ScriptPath = ($($PSScriptRoot) + "\IIS-AppPool-Start.ps1")
        $ScritpContent = (Get-Content -Path $($ScriptPath) -Raw)
        $ScriptBody = @"
$($ScritpContent)
"@
    }    
}

Content Of The IIS-AppPool-Script.ps1 Script

# Self Elevating Administrator Permission
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }

# Convert The Standard Senstive Text Password To Encrypted Password
$SecureStringPassword = ConvertTo-SecureString -String $($UATServiceAccountPassword) -AsPlainText -Force
$Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $($UATServiceAccount), $($SecureStringPassword)

# Stop IIS App Pool On Current Web Server

# Importing WebAdministration Module 
Import-Module -Name "WebAdministration" -Force
      
# Stop IIS Application Pool
$CurrentServer = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName
Write-Output "Stopping $($AppPoolName) Application Pool Is Initiated On Server $($CurrentServer)...`n"

# Get The Number Of Retries
$Retries = "500"

# Get The Number Of Attempts
$Delay = "200"

# Check If Application Pool Exists
if ((Test-Path -Path "IIS:\AppPools\$($AppPoolName)") -eq "True") {

    # Stop App Pool If Not Already Stopped         
    if ((Get-WebAppPoolState -Name $($AppPoolName)).Value -ne "Stopped") {        
        Stop-WebAppPool -Name $($AppPoolName)
        $State = (Get-WebAppPoolState -Name $($AppPoolName)).Value
        $Counter = 1

        # Wait For The App Pool To Be "Stopped"
        do {
            $State = (Get-WebAppPoolState -Name $($AppPoolName)).Value
            Write-Output "$Counter/$Retries Waiting For IIS App Pool $($AppPoolName) To Stop Completely. Current Status Is: $($State)"
            $Counter++
            Start-Sleep -Milliseconds $Delay
        }
        while ($($State) -ne "Stopped" -and ($Counter -le $($Retries)))

        # Throw An Error If The App Pool Is Not Stopped
        if ($($Counter) -gt $($Retries)) {
            throw "Could Not Stop IIS App Pool $($AppPoolName). `nTry To Increase The Number Of Retries $($Retries) Or Delay Between Attempts $($Delay) Milliseconds)."
        }
    }
    else {
        Write-Output "Application Pool $($AppPoolName) Is Already Stopped`n"
    }
}
else {         
    Write-Error "IIS App Pool $($AppPoolName) Doesn't Exist`n"
}            
Write-Output "Stopping $($AppPoolName) Application Pool Is Initiated On Server $($CurrentServer) Is Completed !!!`n"

# Stop IIS App Pool On Sets Of Web Servers
foreach ($Server in $($Servers)) {

    $Session = New-PSSession –ComputerName $($Server) -Credential $($Credential)

    Invoke-Command -Session $($Session) -ArgumentList $($AppPoolName), $($Server) {

        $AppPoolName = $Args[0]
        $Server = $Args[1]

        # Self Elevating Administrator Permission
        if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }

        # Importing WebAdministration Module 
        Import-Module -Name "WebAdministration" -Force
      
        # Stop IIS Application Pool
        Write-Output "Stopping $($AppPoolName) Application Pool Is Initiated On Server $($Server)...`n"

        # Get The Number Of Retries
        $Retries = "500"

        # Get The Number Of Attempts
        $Delay = "200"

        # Check If Application Pool Exists
        if ((Test-Path -Path "IIS:\AppPools\$($AppPoolName)") -eq "True") {

            # Stop App Pool If Not Already Stopped         
            if ((Get-WebAppPoolState -Name $($AppPoolName)).Value -ne "Stopped") {                
                Stop-WebAppPool -Name $($AppPoolName)
                $State = (Get-WebAppPoolState -Name $($AppPoolName)).Value
                $Counter = 1

                # Wait For The App Pool To Be "Stopped"
                do {
                    $State = (Get-WebAppPoolState -Name $($AppPoolName)).Value
                    Write-Output "$Counter/$Retries Waiting For IIS App Pool $($AppPoolName) To Stop Completely. Current Status Is: $($State)"
                    $Counter++
                    Start-Sleep -Milliseconds $Delay
                }
                while ($($State) -ne "Stopped" -and ($Counter -le $($Retries)))

                # Throw An Error If The App Pool Is Not Stopped
                if ($($Counter) -gt $($Retries)) {
                    throw "Could Not Stop IIS App Pool $($AppPoolName). `nTry To Increase The Number Of Retries $($Retries) Or Delay Between Attempts $($Delay) Milliseconds)"
                }
            }
            else {
                Write-Output "Application Pool $($AppPoolName) Is Already Stopped`n"
            }
        }
        else {         
            Write-Error "IIS App Pool $($AppPoolName) Doesn't Exist`n"
        }            
        Write-Output "Stopping $($AppPoolName) Application Pool Is Initiated On Server $($Server) Is Completed !!!`n"
    }
    Remove-PSSession -Session $($Session)
}

Thanks
Vivek

Thanks Vivek, I added that content to the script file and successfully tested, so it’s not the script content. There is one difference that I noted, I put the switch statement OctopusProjectABC into double quotes, "OctopusProjectABC". Would you be able to give that a try to see if the switch statement goes through the correct path?

Regards,

Shawn

Hi,

I have tried to do it the way suggested , however no luck with that. In fact, I have I have moved from switch statement to simpler logic. Also, this is to update you that, I am using octopus deploy cloud version. Along with, I do not see any logical issue as well if I want to use Get-Content and pass the variable value to $ScriptBody.

You have mentioned that it is working for you, Can I have the snippet ? . I am wondering if I can see desired output in json payload then why it is not uploading ?. Is it something octopus cloud version doesn’t support the approach I am doing ?

From

# Construct Notes String
switch ($OctopusProjectName) {
    OctopusProjectABC { 
        $StepNameNoteString = "XYZ"       
        $ScriptPath = ($($PSScriptRoot) + "\IIS-AppPool-Start.ps1")
        $ScritpContent = (Get-Content -Path $($ScriptPath) -Raw)
        $ScriptBody = @"
$($ScritpContent)
"@
    }    
}

To

$ScriptPath = ($($PSScriptRoot) + "\IIS-AppPool-Start.ps1")
$ScritpContent = (Get-Content -Path $($ScriptPath) -Raw)
$ScriptBody = @"
$($ScritpContent)
"@

Thanks
Vivek

One More Point :
If do paste the content of the file between @’ & '@ with out variable interpolation like below then it works. So, It is staange to see it, becuase the previous method we are using does have valid json but still not able to upload.

$ScriptBody = @'
ScriptRawContent
'@

Hi @Shawn_Sesna

I have fix the issue. I have change the foreach loop content in “IIS-AppPool-Stop.ps1” where it was not the issue with original content, it seems that octopus deploy both on-prem and cloud version have some issue with “New-PSSession” command or syntax.

New Content

# Stop IIS App Pool On Sets Of Web Servers
foreach ($Server in $($Servers)) {
    
    $Parameters = @{
        ComputerName = "$($Server)"
        ArgumentList = "$($AppPoolNameUS)"
        ScriptBlock  = { 
            
            $AppPoolNameUS = $Args[0]

            # Self Elevating Administrator Permission
            if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }

            # Importing WebAdministration Module 
            Import-Module -Name "WebAdministration" -Force
      
            # Stop IIS Application Pool
            Write-Output "`nStopping $($AppPoolNameUS) Application Pool Is Initiated On Server $($Server)...`n"

            # Get The Number Of Retries
            $Retries = "500"

            # Get The Number Of Attempts
            $Delay = "200"

            # Check If Application Pool Exists
            if ((Test-Path -Path "IIS:\AppPools\$($AppPoolNameUS)") -eq "True") {

                # Stop App Pool If Not Already Stopped         
                if ((Get-WebAppPoolState -Name $($AppPoolNameUS)).Value -ne "Stopped") {                
                    Stop-WebAppPool -Name $($AppPoolNameUS)
                    $State = (Get-WebAppPoolState -Name $($AppPoolNameUS)).Value
                    $Counter = 1

                    # Wait For The App Pool To Be "Stopped"
                    do {
                        $State = (Get-WebAppPoolState -Name $($AppPoolNameUS)).Value
                        Write-Output "$Counter/$Retries Waiting For IIS App Pool $($AppPoolNameUS) To Stop Completely. Current Status Is: $($State)"
                        $Counter++
                        Start-Sleep -Milliseconds $Delay
                    }
                    while ($($State) -ne "Stopped" -and ($Counter -le $($Retries)))

                    # Throw An Error If The App Pool Is Not Stopped
                    if ($($Counter) -gt $($Retries)) {
                        throw "Could Not Stop IIS App Pool $($AppPoolNameUS). `nTry To Increase The Number Of Retries $($Retries) Or Delay Between Attempts $($Delay) Milliseconds)"
                    }
                }
                else {
                    Write-Output "Application Pool $($AppPoolNameUS) Is Already Stopped`n"
                }
            }
            else {         
                Write-Error "IIS App Pool $($AppPoolNameUS) Doesn't Exist`n"
            }            
            Write-Output "`nStopping $($AppPoolNameUS) Application Pool Is Initiated On Server $($Server) Is Completed !!!`n"
        }        
    }
    Invoke-Command @Parameters
}

OLD CONTENT

# Stop IIS App Pool On Sets Of Web Servers
foreach ($Server in $($Servers)) {

    $Session = New-PSSession –ComputerName $($Server) -Credential $($Credential)

    Invoke-Command -Session $($Session) -ArgumentList $($AppPoolNameUS), $($Server) {

        $AppPoolNameUS = $Args[0]
        $Server = $Args[1]

        # Self Elevating Administrator Permission
        if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }

        # Importing WebAdministration Module 
        Import-Module -Name "WebAdministration" -Force
      
        # Stop IIS Application Pool
        Write-Output "`nStopping $($AppPoolNameUS) Application Pool Is Initiated On Server $($Server)...`n"

        # Get The Number Of Retries
        $Retries = "500"

        # Get The Number Of Attempts
        $Delay = "200"

        # Check If Application Pool Exists
        if ((Test-Path -Path "IIS:\AppPools\$($AppPoolNameUS)") -eq "True") {

            # Stop App Pool If Not Already Stopped         
            if ((Get-WebAppPoolState -Name $($AppPoolNameUS)).Value -ne "Stopped") {                
                Stop-WebAppPool -Name $($AppPoolNameUS)
                $State = (Get-WebAppPoolState -Name $($AppPoolNameUS)).Value
                $Counter = 1

                # Wait For The App Pool To Be "Stopped"
                do {
                    $State = (Get-WebAppPoolState -Name $($AppPoolNameUS)).Value
                    Write-Output "$Counter/$Retries Waiting For IIS App Pool $($AppPoolNameUS) To Stop Completely. Current Status Is: $($State)"
                    $Counter++
                    Start-Sleep -Milliseconds $Delay
                }
                while ($($State) -ne "Stopped" -and ($Counter -le $($Retries)))

                # Throw An Error If The App Pool Is Not Stopped
                if ($($Counter) -gt $($Retries)) {
                    throw "Could Not Stop IIS App Pool $($AppPoolNameUS). `nTry To Increase The Number Of Retries $($Retries) Or Delay Between Attempts $($Delay) Milliseconds)"
                }
            }
            else {
                Write-Output "Application Pool $($AppPoolNameUS) Is Already Stopped`n"
            }
        }
        else {         
            Write-Error "IIS App Pool $($AppPoolNameUS) Doesn't Exist`n"
        }            
        Write-Output "`nStopping $($AppPoolNameUS) Application Pool Is Initiated On Server $($Server) Is Completed !!!`n"
    }
    Remove-PSSession -Session $($Session)
}

Thanks
Vivek

Ah, okay. Glad you were able to get it working :slight_smile:

Regards,

Shawn