Test1
三、添加 Send an HTTP request to SharePoint
在外层 Apply to each 里面,新建一步:
Send an HTTP request to SharePoint
微软官方说明,这个动作就是让你在 Power Automate 里直接构造并执行 SharePoint REST API 请求,特别适合标准 SharePoint 动作处理不了的需求。
这一步要填的参数
Site Address
填旧 list 所在站点,例如:
https://yourcompany.sharepoint.com/sites/yoursite
Method
填:
GET
因为你现在是读取版本历史,不是更新。
Uri
先用这个版本:
_api/web/lists/GetByTitle('Old Approval List')/items(@{items('Apply_to_each')?['ID']})/versions
这里的含义是:
GetByTitle('Old Approval List'):指定旧 listitems(ID):指定当前循环这条旧记录/versions:读取该 item 的版本历史
SharePoint REST 支持对 list 和 list item 做 REST 读取;Microsoft Graph 文档也明确支持 list item versions 读取。
Headers
一般加这两个最稳:
Header 1
- Key:
Accept
- Value:
application/json;odata=nometadata
Header 2
- Key:
Content-Type
- Value:
application/json;odata=nometadata
GET 请求里 Content-Type 不是绝对必须,但这样写通常比较整齐。微软关于 Send an HTTP request to SharePoint 的指导就是按 REST/JSON 请求来用。
Body
留空。
因为 GET 请求不需要 body。
四、先做最小测试
先不要急着解析。
你先加一个:
Compose
内容填:
body('Send_an_HTTP_request_to_SharePoint')
然后跑 1 条测试,看看返回长什么样。
你要重点看:
- 是否有多个 version
- 每个 version 里有没有你的 comment 字段
- 字段名到底叫
Comment还是别的内部名
五、如果想更精准地取字段,URI 可以升级
如果你已经知道 comment 的内部名就是 Comment,可以试这个更具体的 URI:
_api/web/lists/GetByTitle('Old Approval List')/items(@{items('Apply_to_each')?['ID']})/versions?$select=VersionLabel,Created,Comment
这表示只取:
VersionLabelCreatedComment
SharePoint REST 支持 OData 风格的 $select。
如果字段内部名不是 Comment,就把这里改成真实内部名。
六、如果你还想拿到是谁写的
可以试:
_api/web/lists/GetByTitle('Old Approval List')/items(@{items('Apply_to_each')?['ID']})/versions?$select=VersionLabel,Created,Comment,Editor/Title&$expand=Editor
这里意思是:
$expand=Editor- 同时
$select=Editor/Title
SharePoint REST 对用户等复杂字段支持 $expand。
七、如果返回里看不到 Comment 字段怎么办
那通常有三种可能:
1)内部名不是 Comment
最常见。
你需要先确认内部名。
2)该字段在 versions 里不是直接暴露
这类 append 字段本来就比较特殊,微软也明确提到它在 workflow/output 场景里会出现空白或特殊行为。
3)你需要改用更细的版本接口
可以先取版本列表,再逐个取某个 version 的 fields。Microsoft Graph 文档明确支持:
GET /sites/{site-id}/lists/{list-id}/items/{item-id}/versions/{version-id}
并可展开 fields。
不过在 Power Automate 里,先试 SharePoint REST 的 /versions 通常更直接。
八、你现在最推荐的测试配置
假设:
- 旧 list 名:
Old Approval List - 外层循环名:
Apply_to_each - comment 内部名:
Comment
那你这一步可以直接这样填:
Action
Send an HTTP request to SharePoint
Site Address
https://yourcompany.sharepoint.com/sites/yoursite
Method
GET
Uri
_api/web/lists/GetByTitle('Old Approval List')/items(@{items('Apply_to_each')?['ID']})/versions?$select=VersionLabel,Created,Comment
Headers
Accept: application/json;odata=nometadata
Content-Type: application/json;odata=nometadata
Body
留空
然后紧接着加一个:
Compose
body('Send_an_HTTP_request_to_SharePoint')
九、你跑完后应该看什么
在 run history 里打开这一步,重点看:
- 返回是不是一个数组
- 是否有多条 version
- 每条 version 里
Comment有没有值 - 这些值是否分别对应历史 comment
如果你看到:
- 某些 version 的
Comment有值 - 而且不同 version 的值不同
那就说明这条路是对的,你后面就可以把它们拼接起来。
十、下一步会是什么
如果这个 HTTP request 成功拿到了多条 version comment,下一步就是:
Parse JSONApply to each遍历 versions- 用
Append to string variable或Select + Join把 comment 拼成完整文本 - 再
Create item到新 list
concat(‘_api/web/lists/GetByTitle(”你的旧List名称”)/items(‘, string(item()?[‘ID’]), ‘)/versions’)
======================================================
第 2 步:加一个 Parse JSON
在 HTTP 这一步后面,新增:
Parse JSON
Content
填这个动态内容:
body(‘Send_an_HTTP_request_to_SharePoint’)?[‘value’]
因为 /versions 返回的是一个数组,通常就在 value 里。Send an HTTP request to SharePoint 示例里也是先从 body 中取 value 再用于后续循环。
Schema
最简单的方法是:
先跑一次成功测试
复制 HTTP 返回里的 value 数组样本
在 Parse JSON 里点 Generate from sample
把样本粘进去
这样 Power Automate 会自动生成 schema。
第 3 步:初始化一个字符串变量
新增:
Initialize variable
参数这样填:
Name:FullCommentHistory
Type:String
Value:留空
后面用它来累计拼接所有版本的 comment。
第 4 步:加一个新的 Apply to each
这一步是遍历每一个 version。
输入
填:
body(‘Parse_JSON’)
或者直接选 Parse JSON 的输出数组。
因为 Parse JSON 后你拿到的是版本数组,Apply to each 就逐条处理。
第 5 步:在这个 versions 循环里拼接字符串
在内层循环里加:
Append to string variable
Name
选:
FullCommentHistory
Value
建议先用这个版本:
concat(
‘Version: ‘,
coalesce(item()?[‘VersionLabel’], ”),
‘ | Time: ‘,
coalesce(item()?[‘Created’], ”),
‘ | By: ‘,
coalesce(item()?[‘Editor’]?[‘Title’], ”),
decodeUriComponent(‘%0D%0A’),
‘Comment: ‘,
coalesce(item()?[‘Comment’], ”),
decodeUriComponent(‘%0D%0A’),
decodeUriComponent(‘%0D%0A’)
)
这里的作用是把每一个 version 拼成类似:
Version: 3.0 | Time: 2026-01-10T08:00:00Z | By: Alice
Comment: xxxxx
然后空两行,再拼下一条。
concat、coalesce 和 decodeUriComponent(‘%0D%0A’) 这种换行写法,都是 Power Automate/Logic Apps 标准表达式函数。
你可能还要做的一步:排序
/versions 返回的顺序不一定正好就是你最想要的阅读顺序。
所以跑完一次后,你需要看一下:
是从旧到新
还是从新到旧
如果顺序正好反了,你有两个选择:
方案 A
先直接接受当前顺序,后面人工看。
方案 B
在拼接前先做一次数组排序/重组。
这在 Power Automate 里会更复杂一点,所以我建议你先跑一条测试,确认实际顺序再决定。
最后一步:把拼好的内容写到新 list
等 versions 全部循环完后,你的变量 FullCommentHistory 就是完整的 comment 历史文本了。
接下来你在 Create item 或 Update item 里,把目标字段填成:
variables(‘FullCommentHistory’)
不过这里还是要提醒:
即使你把完整历史拼好了,再写回新 list 的原 Comment 列,也通常不会恢复成原来的“逐条 append 历史界面”。
更现实的效果是:把这整段历史作为一块文本写进去,或者作为一条新的追加记录写进去。因为 append-only comment 的旧历史本来就是依赖版本历史来展示的。
给你一版最实用的简化方案
如果你现在只想验证是否成功拿到了完整历史,先做这 3 步就够:
Send an HTTP request to SharePoint
Parse JSON
Apply to each → Append to string variable
等 FullCommentHistory 看起来对了,再决定写回哪里。
你现在可以直接抄的关键表达式
Parse JSON 的 Content
body(‘Send_an_HTTP_request_to_SharePoint’)?[‘value’]
内层 Apply to each 的输入
body(‘Parse_JSON’)
Append to string variable 的 Value
concat(
‘Version: ‘,
coalesce(item()?[‘VersionLabel’], ”),
‘ | Time: ‘,
coalesce(item()?[‘Created’], ”),
‘ | By: ‘,
coalesce(item()?[‘Editor’]?[‘Title’], ”),
decodeUriComponent(‘%0D%0A’),
‘Comment: ‘,
coalesce(item()?[‘Comment’], ”),
decodeUriComponent(‘%0D%0A’),
decodeUriComponent(‘%0D%0A’)
)
======================================================
你现在的前提是:
- 你已经有一个 Send an HTTP request to SharePoint,并且成功返回了
/versions的结果 - 你的 Parse JSON → Content 用的是
body('Send_an_HTTP_request_to_SharePoint') - 返回体里真正要遍历的是
value数组;Microsoft 的 SharePoint HTTP 指南也明确给了这种模式:当响应里有数组时,循环输入通常用body('Send_an_HTTP_request_to_SharePoint')['value']。
1. Parse JSON
新增动作:Parse JSON
Content
填:
body('Send_an_HTTP_request_to_SharePoint')
Schema
填这一份:
{
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"type": "object",
"properties": {
"VersionLabel": {
"type": "string"
},
"Created": {
"type": "string"
},
"Comment": {
"type": "string"
},
"Editor": {
"type": "object",
"properties": {
"Title": {
"type": "string"
}
}
}
}
}
}
}
}
这样写是因为你传给 Parse JSON 的是整个 body,不是 body(...)?['value'];所以 schema 最外层必须是 object,里面再有一个 value 数组。Parse JSON 的作用本来就是把 JSON 解析成后续更容易引用的结构化输出。
2. Initialize variable
新增动作:Initialize variable
Name
FullCommentHistory
Type
String
Value
留空
这是为了后面把每个版本的 comment 历史逐条拼接起来。Data Operations 里的变量、Compose、Select、Join 这类动作本来就是为这类 JSON/数组/字符串处理准备的。
3. Apply to each(遍历 versions)
新增动作:Apply to each
Select an output from previous steps
填:
body('Parse_JSON')?['value']
这是最关键的一步。
Microsoft 的 SharePoint HTTP 指南明确写了:如果响应里是数组,循环输入用 body('Send_an_HTTP_request_to_SharePoint')['value'] 这一类表达式;你现在已经先做了 Parse JSON,所以这里对应改成 body('Parse_JSON')?['value']。循环内部再用 items('Apply_to_each') 或 item() 访问单条元素。
4. Append to string variable(在版本循环里拼接)
在这个 versions 循环里面,新增动作:Append to string variable
Name
FullCommentHistory
Value
填这个表达式:
concat(
'Version: ',
coalesce(item()?['VersionLabel'], ''),
' | Time: ',
coalesce(item()?['Created'], ''),
' | By: ',
coalesce(item()?['Editor']?['Title'], ''),
decodeUriComponent('%0D%0A'),
'Comment: ',
coalesce(item()?['Comment'], ''),
decodeUriComponent('%0D%0A'),
decodeUriComponent('%0D%0A')
)
这里用到的 concat、coalesce 都是标准表达式函数;decodeUriComponent('%0D%0A') 是常见换行写法。Expression functions 文档对这类函数都有定义。
跑出来后的效果大概像这样:
Version: 3.0 | Time: 2026-03-01T10:00:00Z | By: Alice
Comment: some textVersion: 2.0 | Time: 2026-02-01T08:00:00Z | By: Bob
Comment: older text
5. 可选:Compose(先检查拼接结果)
在 versions 循环结束后,你可以先加一个 Compose,确认拼接结果是不是你想要的。
Inputs
variables('FullCommentHistory')
这样你先在运行历史里确认内容是否完整,再决定写回新 list。Compose 本身就是用来构造和检查输出的标准 Data Operation。
6. Create item(写入新 list)
接下来是 Create item。
这一步仍然放在你外层“旧 list item”的循环里,也就是每处理完一条旧 item 的 version history,就创建一条新 item。
Site Address
选你的新 list 所在站点
List Name
选你的新 list
各字段怎么填
你原来怎么迁移其他字段,就继续怎么映射。关键是 Comment 这一列:
如果你想把完整历史写进原 Comment 列
在 Comment 字段里填:
variables('FullCommentHistory')
如果你后来新建了一个普通文本列,例如 Comment_Migrated
那就在那个字段里填:
variables('FullCommentHistory')
Create item 是 SharePoint 连接器的标准动作,用于在目标 list 创建一条新记录。
7. 重要提醒:写回原 Comment 列会是什么效果
如果你的目标 Comment 列仍然是 Append changes to existing text,那就要有心理预期:
- 你写进去的这整段历史,通常会作为一条新的值/一条新的追加内容出现
- 不会自动还原成原来那种逐条历史线程界面
你现在能做的是“把完整历史文本取出来并保存”,但不是“重建 SharePoint 原生 append 历史机制”。这一点和你前面遇到的问题本质一致:append 历史真正依赖的是版本历史,而不是单个当前字段值。你现在这条 /versions 路线,本质上就是在自己重建这段历史文本。
8. 最终的动作顺序
按顺序应该是这样:
- Get items(旧 list)
- Apply to each(旧 list 的每一条 item)
- Send an HTTP request to SharePoint(取当前旧 item 的 versions)
- Parse JSON
- Initialize variable
FullCommentHistory - Apply to each(遍历
body('Parse_JSON')?['value']) - Append to string variable
- Create item(新 list)
- Comment =
variables('FullCommentHistory')
- Comment =
9. 你可以直接抄的关键表达式汇总
Parse JSON → Content
body('Send_an_HTTP_request_to_SharePoint')
外层版本循环 → 输入
body('Parse_JSON')?['value']
版本循环里拼接 comment 历史
concat(
'Version: ',
coalesce(item()?['VersionLabel'], ''),
' | Time: ',
coalesce(item()?['Created'], ''),
' | By: ',
coalesce(item()?['Editor']?['Title'], ''),
decodeUriComponent('%0D%0A'),
'Comment: ',
coalesce(item()?['Comment'], ''),
decodeUriComponent('%0D%0A'),
decodeUriComponent('%0D%0A')
)
Create item → Comment
variables('FullCommentHistory')
================================
{
“type”: “object”,
“properties”: {
“value”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“VersionLabel”: {
“type”: [“string”, “null”]
},
“Created”: {
“type”: [“string”, “null”]
},
“Comment”: {
“type”: [“string”, “null”]
},
“Editor”: {
“type”: [“object”, “null”],
“properties”: {
“Title”: {
“type”: [“string”, “null”]
}
}
}
}
}
}
}
}