foreach takes 'do step' optionally
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ForeachWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ForeachWorkflowStep.java
index e4a511c..0d205be 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ForeachWorkflowStep.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ForeachWorkflowStep.java
@@ -21,13 +21,14 @@
 import org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
 import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext;
 import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 
 import java.util.Map;
 
 public class ForeachWorkflowStep extends CustomWorkflowStep {
 
-    public static final String SHORTHAND = "${target_var_name} [ \" in \" ${target...} ]";
+    public static final String SHORTHAND = "${target_var_name} [ \" in \" ${target...} [ \" do \" ${step...} ] ]";
 
     public static final String SHORTHAND_TYPE_NAME_DEFAULT = "foreach";
 
@@ -43,10 +44,16 @@
     @Override
     public void populateFromShorthand(String value) {
         if (input==null) input = MutableMap.of();
-        populateFromShorthandTemplate(SHORTHAND, value);
+        populateFromShorthandTemplate(SHORTHAND, value, true, true);
 
         if (input.containsKey("target")) target = input.remove("target");
         target_var_name = input.remove("target_var_name");
+
+        if (input.containsKey("step")) {
+            Object step = input.remove("step");
+            if (this.steps!=null) throw new IllegalArgumentException("Cannot set step in shorthand as it is set elsewhere");
+            this.steps = MutableList.of(step);
+        }
     }
 
     protected Iterable checkTarget(Object targetR) {
diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowNestedAndCustomExtensionTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowNestedAndCustomExtensionTest.java
index 20f59d5..245cdd6 100644
--- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowNestedAndCustomExtensionTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowNestedAndCustomExtensionTest.java
@@ -910,11 +910,11 @@
     }
 
     @Test
-    public void testForeachCondition() throws Exception {
+    public void testForeachConditionAndShorthandStep() throws Exception {
         Object output = invokeWorkflowStepsWithLogging(MutableList.of(
                 "let list L = [ a, b, c ]",
-                MutableMap.of("step", "foreach item in ${L}",
-                        "steps", MutableList.of("return ${item}"),
+                MutableMap.of("step", "foreach item in ${L} do return ${item}",
+                        //"steps", MutableList.of("return ${item}"),
                         "condition", MutableMap.of("any", MutableList.of(
                                 "a",
                                 MutableMap.of("target", MutableList.of("x", "c"),
@@ -924,4 +924,14 @@
                                 ))))));
         Asserts.assertEquals(output, MutableList.of("a", "c"));
     }
+
+    @Test
+    public void testForeachStepsDuplication() throws Exception {
+        Asserts.assertFailsWith(() -> invokeWorkflowStepsWithLogging(MutableList.of(
+                "let list L = [ a, b, c ]",
+                MutableMap.of("step", "foreach item in ${L} do return 1",
+                        "steps", MutableList.of("return 0")
+                                ))),
+            Asserts.expectedFailureContainsIgnoreCase("cannot set step", "shorthand", "elsewhere"));
+    }
 }