# Add skills and PowerShell script > given the flow described in "User Workflow" secion of the document, and considering the whole document for a more detailed description, I want you to: > > - add appropriate skills > - create a Powershell script that does what is described
15 previous messages > I’ll inspect the workspace instructions and locate the document’s “User Workflow” section first. Then I’ll translate the full specification into the appropriate Codex skills and a PowerShell implementation, with runnable verification. > >
Ran a command > > - Ran `Get-ChildItem -Force | Select-Object Mode,Length,LastWriteTime,Name; rg --files -g 'AGENTS.md' -g '*.md' -g '*.txt' -g '*.docx' -g '*.pdf'` > >
> > The request includes creating reusable skills, so I’m applying the `skill-creator` guidance before making changes. I found the specification in `save-metadata-schema-automatic.md`; I’ll read it in full, including its workflow and validation requirements. > >
Ran 3 commands > > - Ran `Get-Content -Raw 'C:\Users\pierp\.codex\skills\.system\skill-creator\SKILL.md'; Get-Content -Raw '.\save-metadata-schema-automatic.md'` > - Ran `$lines = Get-Content '.\save-metadata-schema-automatic.md'; "LINES=$($lines.Count)"; rg -n '^#{1,4} ' '.\save-metadata-schema-automatic.md'` > - Ran `$lines = Get-Content '.\save-metadata-schema-automatic.md'; $lines[0..493]` > >
> > The specification is precise about request construction, but the save payload is the fragile part. I’m checking the supplied HAR structurally—without printing session cookies—to reproduce the real multipart field format, headers, and response behavior instead of guessing from prose. > >
Ran a command > > - Ran `$har = Get-Content -Raw '.\save-metadata-schema.har' | ConvertFrom-Json -Depth 100; $entries = @($har.log.entries | Where-Object { $_.request.url -match '/AJSRV/metadata/(list|search|save)' }); foreach ($entry in $entries) { $request = $entry.request; $safeHeaders = @($request.headers | Where-Object { $_.name -notmatch '^(Cookie|Authorization)$' } | Select-Object name,value); [pscustomobject]@{ Method=$request.method; Url=$request.url; Headers=$safeHeaders; PostMime=$request.postData.mimeType; Params=$request.postData.params; Text=$request.postData.text; Status=$entry.response.status; ResponseMime=$entry.response.content.mimeType } | ConvertTo-Json -Depth 20 }` > >
> > The HAR reveals one important correction to the prose: save field names use the row’s generic-schema/data ID (the second search column, e.g. `81825`), not the first `ID_OBJECT` value. I’ll encode that verified invariant in both the script and the reusable skill. The script will also preserve column order, fetch every result page, back up all search responses before any write, require an explicit destructive confirmation, and summarize per-row outcomes. > >
Ran 3 commands > > - Ran `Get-Content -Raw 'C:\Users\pierp\.codex\skills\.system\skill-creator\references\openai_yaml.md'; git status --short` > - Ran `python 'C:\Users\pierp\.codex\skills\.system\skill-creator\scripts\init_skill.py' elixforms-schema-bulk-updater --path '.\.agents\skills' --resources scripts,references --interface 'display_name=ElixForms Metadata Bulk Update' --interface 'short_description=Safely update one metadata field in bulk' --interface 'default_prompt=Use $elixforms-schema-bulk-updater to prepare or run a guarded ElixForms metadata bulk update.'` > - Ran `Get-Content -Raw '.\.agents\skills\elixforms-schema-bulk-updater\SKILL.md'; Get-Content -Raw '.\.agents\skills\elixforms-schema-bulk-updater\agents\openai.yaml'; Get-ChildItem -Recurse '.\.agents\skills\elixforms-schema-bulk-updater' | Select-Object FullName` > >
> > I’ve finished the design pass. The skill will be repository-local and self-contained under `.agents/skills`, with the full PowerShell implementation packaged as its deterministic helper plus a short root launcher. No credentials will be persisted; backups contain search data and filters but never the cookie. > > The initial patch hit a patch-tool constraint while replacing the generated scaffold; no implementation files were partially written. I’m splitting the change into smaller atomic patches and continuing. > >
Created 3 files, edited a file, ran a command > > - Wrote `./.agents/skills/elixforms-schema-bulk-updater/SKILL.md` (+20 -6) > - Wrote `./.agents/skills/elixforms-schema-bulk-updater/references/protocol.md` (+37 -0) > - Wrote `./Invoke-ElixFormsMetadataBulkUpdate.ps1` (+1 -0) > - Ran `Get-Content -Raw '.\.agents\skills\elixforms-schema-bulk-updater\SKILL.md'; Test-Path '.\.agents\skills\elixforms-schema-bulk-updater\references\protocol.md'; Test-Path '.\Invoke-ElixFormsMetadataBulkUpdate.ps1'` > - Wrote `./.agents/skills/elixforms-schema-bulk-updater/scripts/Invoke-ElixFormsMetadataBulkUpdate.ps1` (+653 -0) > >
> > The implementation is now in place. I’m at the verification stage: parsing it with PowerShell’s AST parser, exercising URI encoding and multipart byte construction against the documented examples, checking the root launcher, and validating the skill package for unfinished scaffold content or metadata errors. > >
Ran 4 commands > > - Ran ``$scriptPath = Resolve-Path '.\.agents\skills\elixforms-schema-bulk-updater\scripts\Invoke-ElixFormsMetadataBulkUpdate.ps1'; $tokens = $null; $parseErrors = $null; $ast = [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$tokens, [ref]$parseErrors); if ($parseErrors.Count -gt 0) { $parseErrors | Format-List; exit 1 }; 'PowerShell parse: OK'; . $scriptPath; $query = [ordered]@{'SEARCH_IN[]'=616; 'S_616_NEW_COL0014'='antani tapioca'; PAGE=1; AJL='it'; PAGESIZE=80; SECURE='true'}; $uri = New-ElixFormsUri -BaseUri 'https://console-unipr.elixforms.it/AJSRV/metadata' -Route 'search' -Query $query; "URI=$uri"; if ($uri.AbsoluteUri -notmatch 'SEARCH_IN%5B%5D=616' -or $uri.AbsoluteUri -notmatch 'antani%20tapioca') { throw 'Query encoding test failed.' }; $multipart = New-MultipartFieldBody -Name 'S_616_81825_COL0014' -Value ''; $multipartText = [Text.Encoding]::UTF8.GetString($multipart.Bytes); if ($multipartText -notmatch 'name="S_616_81825_COL0014"' -or -not $multipartText.EndsWith("--`r`n")) { throw 'Multipart test failed.' }; "Multipart bytes=$($multipart.Bytes.Length) content-type=$($multipart.ContentType)"; 'Helper tests: OK'; python 'C:\Users\pierp\.codex\skills\.system\skill-creator\scripts\quick_validate.py' '.\.agents\skills\elixforms-schema-bulk-updater'; rg -n '\[TODO|TODO:' '.\.agents\skills\elixforms-schema-bulk-updater' '.\Invoke-ElixFormsMetadataBulkUpdate.ps1'`` > - Ran ``$scriptPath = Resolve-Path '.\.agents\skills\elixforms-schema-bulk-updater\scripts\Invoke-ElixFormsMetadataBulkUpdate.ps1'; $search = Get-Content -Raw '.\search-schema-results.json' | ConvertFrom-Json; "Fixture columns=$(@($search.columns).Count) rows=$(@($search.data).Count) total=$($search.totalRecords) pages=$($search.numPages)"; $genericKey = 'S_616_ID_GENERICSCHEMA'; $genericIndex = [Array]::IndexOf([object[]]@($search.columns.key), $genericKey); $objectIndex = [Array]::IndexOf([object[]]@($search.columns.key), 'ID_OBJECT'); "Fixture ID indexes: object=$objectIndex generic-data=$genericIndex"; if ($genericIndex -lt 0 -or $objectIndex -lt 0) { throw 'Fixture identifier columns missing.' }; $firstRow = @($search.data)[0]; "First save-name example=S_616_$($firstRow[$genericIndex])_COL0014 (object $($firstRow[$objectIndex]))"; if (Get-Command Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) { Invoke-ScriptAnalyzer -Path $scriptPath -Severity Warning,Error | Format-Table -AutoSize } else { 'PSScriptAnalyzer not installed; skipped.' }; if (Get-Command powershell.exe -ErrorAction SilentlyContinue) { powershell.exe -NoProfile -Command "`$tokens=`$null; `$errors=`$null; [System.Management.Automation.Language.Parser]::ParseFile('$($scriptPath.Path.Replace("'", "''"))',[ref]`$tokens,[ref]`$errors) > `$null; if (`$errors.Count) { `$errors | Out-String; exit 1 }; 'Windows PowerShell parse: OK'" }`` > - Ran `$failed = $false; Get-ChildItem -Recurse -Filter '*.ps1' | ForEach-Object { $tokens=$null; $errors=$null; [System.Management.Automation.Language.Parser]::ParseFile($_.FullName,[ref]$tokens,[ref]$errors) > $null; if ($errors.Count) { $failed=$true; $errors | ForEach-Object { "PARSE ERROR $($_.Extent.File):$($_.Extent.StartLineNumber): $($_.Message)" } } else { "PARSE OK $($_.FullName)" } }; if ($failed) { exit 1 }; Get-ChildItem -Recurse '.\.agents\skills\elixforms-schema-bulk-updater' | Select-Object FullName,Length; Get-Content -Raw '.\.agents\skills\elixforms-schema-bulk-updater\agents\openai.yaml'` > - Ran `try { throw 'Value {0}' -f 42 } catch { "THROW_TEST=$($_.Exception.Message)" }; . '.\.agents\skills\elixforms-schema-bulk-updater\scripts\Invoke-ElixFormsMetadataBulkUpdate.ps1'; $sample = Get-Content -Raw '.\search-schema-results.json' | ConvertFrom-Json; $columns = @($sample.columns); $rows = @($sample.data); "RowsRuntimeType=$($rows.GetType().FullName) FirstRowType=$($rows[0].GetType().FullName) FirstRowCount=$(@($rows[0]).Count)"; $objectIndex = Get-ColumnIndex -Columns $columns -Key 'ID_OBJECT'; $genericIndex = Get-ColumnIndex -Columns $columns -Key 'S_616_ID_GENERICSCHEMA'; "FunctionIndexes=$objectIndex,$genericIndex"; $schemaFixture = [pscustomobject]@{ sections = @([pscustomobject]@{key='SEC_B'; title='B'; orderKey='ORD002'; fields=@([pscustomobject]@{key='COL_B';title='B';type='STRING';orderKey='ORD001'})},[pscustomobject]@{key='SEC_A';title='A';orderKey='ORD001';fields=@([pscustomobject]@{key='COL_A2';title='A2';type='STRING';orderKey='ORD002'},[pscustomobject]@{key='COL_A1';title='A1';type='STRING';orderKey='ORD001'})})}; $ordered = @(Get-OrderedSchemaFields -Schema $schemaFixture); "OrderedFields=$($ordered.Key -join ',')"; if (($ordered.Key -join ',') -ne 'COL_A1,COL_A2,COL_B') { throw 'Field ordering test failed.' }` > >
> > The verification checks are clean in both PowerShell 7 and Windows PowerShell 5.1, and the supplied 56-row fixture maps identifiers as expected. I’m making one final hardening pass on backup naming and inline help, then I’ll hand over the exact run command and the HAR-derived identifier caveat. > >
Edited 2 files, ran a command > > - Wrote `./.agents/skills/elixforms-schema-bulk-updater/scripts/Invoke-ElixFormsMetadataBulkUpdate.ps1` (+25 -1) > - Wrote `./Invoke-ElixFormsMetadataBulkUpdate.ps1` (+5 -0) > - Ran ``$allGood = $true; Get-ChildItem -Recurse -Filter '*.ps1' | ForEach-Object { $tokens=$null; $errors=$null; [System.Management.Automation.Language.Parser]::ParseFile($_.FullName,[ref]$tokens,[ref]$errors) > $null; if ($errors.Count) { $allGood=$false; $errors | Format-List } }; if (-not $allGood) { exit 1 }; . '.\.agents\skills\elixforms-schema-bulk-updater\scripts\Invoke-ElixFormsMetadataBulkUpdate.ps1'; $query=[ordered]@{'SEARCH_IN[]'=616;'S_616_NEW_COL0014'='antani tapioca';PAGE=1;AJL='it';PAGESIZE=80;SECURE='true'}; $uri=New-ElixFormsUri -BaseUri 'https://console-unipr.elixforms.it/AJSRV/metadata' -Route 'search' -Query $query; if ($uri.AbsoluteUri -notmatch 'SEARCH_IN%5B%5D=616' -or $uri.AbsoluteUri -notmatch 'antani%20tapioca') { throw 'URI test failed' }; $part=New-MultipartFieldBody -Name 'S_616_81825_COL0014' -Value ''; if ($part.Bytes.Length -le 0 -or $part.ContentType -notmatch '^multipart/form-data; boundary=') { throw 'Multipart test failed' }; python 'C:\Users\pierp\.codex\skills\.system\skill-creator\scripts\quick_validate.py' '.\.agents\skills\elixforms-schema-bulk-updater'; 'Final validation: OK'; rg -n '^function (Get-ElixFormsSearchResult|Save-SearchBackup|Invoke-ElixFormsMetadataBulkUpdate)|generic-schema/data ID|^Use `scripts' '.\.agents\skills\elixforms-schema-bulk-updater\SKILL.md' '.\.agents\skills\elixforms-schema-bulk-updater\references\protocol.md' '.\.agents\skills\elixforms-schema-bulk-updater\scripts\Invoke-ElixFormsMetadataBulkUpdate.ps1'; Get-ChildItem -Recurse -File '.\.agents\skills\elixforms-schema-bulk-updater','Invoke-ElixFormsMetadataBulkUpdate.ps1' | Select-Object FullName,Length`` > >
Implemented the complete workflow. - Added the repository-local skill: [SKILL.md](<./.agents/skills/elixforms-schema-bulk-updater/SKILL.md:1>) - Created the executable launcher: [Invoke-ElixFormsMetadataBulkUpdate.ps1](<./Invoke-ElixFormsMetadataBulkUpdate.ps1:1>) - Added protocol documentation, including the HAR-confirmed requirement to use `ID_GENERICSCHEMA`/`dataId` rather than `ID_OBJECT` when constructing save field names. Run it with: ```powershell .\Invoke-ElixFormsMetadataBulkUpdate.ps1 ``` The script includes hidden cookie input, schema validation, ordered filters, pagination, result preview, two confirmations, timestamped JSON backups, multipart saves with byte-accurate `Content-Length`, failure-safe stopping, summaries, and optional verification. Validated with PowerShell 7 and Windows PowerShell 5.1 parsing, fixture-based identifier checks, URI/multipart tests, and the Codex skill validator. The live API was not called because that requires your authenticated cookie and would modify production data.