1

As my title says I am trying to get following actions taken on Azure release pipeline:

  1. Open .zip file
  2. Find the JSON file with this name
  3. Edit it and save it
  4. Close the .zip file

Also, as my title says I managed to make it work locally, and it works. However, when I add PowerShell task

Run a PowerShell script on Linux, macOS, or Windows

to my Azure release pipeline with code below, it will throw exception:

Exception calling "ReadAllText" with "2" argument(s): "Could not find file 'C:\azagent\A1\_work\r17\a\appsettings.Production.json'."
At C:\azagent\A1\_work\_temp\75ed88f3-4260-42d8-afba-43d0aa0f6a93.ps1:40 char:1+ $JsonData = [IO.File]::ReadAllText($configFile, [Text.Encoding]::GetEncoding(
125 ...

I have tried everything as seen in my code snippet, but could not make it work:

# cd to the agent artifacts directory (where the zip file exist)
cd $env:Agent_ReleaseDirectory
cd "_MyProject.ProjectApi-CI\drop"

$fileToEdit = "appsettings.Production.json"

[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem");
# Open zip and find the particular file (assumes only one inside the Zip file)
$zipfileName = dir -filter '*.zip'
$zip =  [System.IO.Compression.ZipFile]::Open($zipfileName.FullName, "Update")

# $configFile = $zip.Entries.Where({$_.name -like $fileToEdit}) | Select-Object -first 1
$configFile = $zip.Entries.Where({$_.name -like $fileToEdit}) 

# ==================================================================================

# Read the contents of the file
# and IMMEDIATELY Convert file to JSON
$JsonData = [IO.File]::ReadAllText($configFile, [Text.Encoding]::GetEncoding(1252)) | ConvertFrom-Json

# Read the contents of the file
# $content = Get-Content -Path $configFile

# Convert file to JSON
# $JsonData = $text | ConvertFrom-Json

# List all of the Properties of this JSON file
#($JsonData | Get-Member -Type NoteProperty).Name

# Access property by name
# $JsonData.AppSettings.IsDebug

# Change selected variables with new values
$JsonData.AppSettings.setting1 = $true
$JsonData.AppSettings.setting2 = "Some text here"
$JsonData.AppSettings.setting3 = "Some more text here"


# update (json) file with new (json) content (this works only when there is not zip file stuff)
# $JsonData | ConvertTo-Json | Set-Content $configFile

# This will return "PSCustomObject"
# $JsonData.GetType();

# In order to write content of "$JsonData" variable, I need to convert it to string 
# It would also work with "$desiredFile.Write($jsonString | ConvertTo-Json)" but depth is only 2 by default
$jsonString = ConvertTo-Json -InputObject $JsonData -Depth 5
# $jsonString

# ==================================================================================

# Update file with new content
$desiredFile = [System.IO.StreamWriter]($configFile).Open()
$desiredFile.BaseStream.SetLength(0)

# Insert the $text to the file and close
$desiredFile.Write($jsonString)
$desiredFile.Flush()
$desiredFile.Close()

# Write the changes and close the zip file
$zip.Dispose()

So the issue lies in line:

$JsonData = [IO.File]::ReadAllText($configFile, [Text.Encoding]::GetEncoding(1252)) | ConvertFrom-Json

But I could not find any other way to read text from .zip. For example Get-Content requires path. From what I am understanding it is throwing exception because [IO.File]::ReadAllText expects path as second variable, but then again, why is it working locally ?

EDIT: I have the answer why is it working locally. When you open up the file locally with WinRar it will add this file to the temp folder. That's why it is working locally. However, at the time of this writing, I have not found a way to do it as there is something wrong with Powershell versions on Azure Release pipelines.

Kadaj
  • 615
  • 3
  • 13
  • 31
  • Is this to do something with local (windows) and devops pool vm (linux)? – Dilly B Nov 24 '22 at 09:30
  • No, both machines are Windows machines. IIS manage app preset on Azure DevOps. – Kadaj Nov 24 '22 at 09:47
  • Are you using self hosted agent? You could use a zip extractor task to unzip files. – Dilly B Nov 24 '22 at 11:09
  • I am doing it like so, because this is the only way unfortunately due to how project is setup. It has to be replaced on release pipeline. I cannot extract zip due to it being artifact copied to the folder on server. I have to change this file and put it again in the zip file. – Kadaj Nov 24 '22 at 11:19
  • if you're lucky(depends on your hosted agent) there is a PS command - ```Expand-Archive -Force C:\path\to\archive.zip C:\where\to\extract\to``` or even Win10 supports 'tar' command. you could use(try) any one of them. – Dilly B Nov 24 '22 at 11:31
  • Try Add-Type -assembly System.IO.Compression.FileSystem – Dilly B Nov 24 '22 at 11:35
  • I have, and unfortunately it drops same error.. – Kadaj Nov 24 '22 at 11:39

1 Answers1

1

Not sure if this will solve your issue but for sure is more compatible with every system which may be why your current code is failing. This uses the ZipArchive Class instead of ZipFile to open the Zip entry stream then that wrapped stream is read with a StreamReader. This method should work in all PowerShell versions starting from Windows PowerShell 5.1 up to PowerShell Preview 7.3.0-rc.1.

Add-Type -AssemblyName System.IO.Compression

$encoding   = [Text.Encoding]::GetEncoding(1252)
$file       = Get-Item .\myZip.zip
$fileStream = $file.Open([IO.FileMode]::Open)
$zipArchive = [IO.Compression.ZipArchive]::new($fileStream, [IO.Compression.ZipArchiveMode]::Update)
# If you know the exact relative path to the file, use this method
# otherwise you can still use your `.Where{ $_.Name -like ... }` method
$zipEntry   = $zipArchive.GetEntry('dir1/dir2/myJson.json')
$zipStream  = $zipEntry.Open()
$reader     = [IO.StreamReader]::new($zipStream, $encoding)
$JsonData = $reader.ReadToEnd() | ConvertFrom-Json
$JsonData.AppSettings.setting1 = $true
$JsonData.AppSettings.setting2 = "Some text here"
$JsonData.AppSettings.setting3 = "Some more text here"
$json   = ConvertTo-Json -InputObject $JsonData -Depth 5
$zipStream.SetLength(0) # => Starts writing from the beginning of the stream
$writer = [IO.StreamWriter]::new($zipStream, $encoding)
$writer.BaseStream.SetLength(0)
$writer.Write($json)
$writer, $reader, $zipEntry, $zipArchive, $fileStream | ForEach-Object Dispose
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Hey man, thanks a ton for trying. I figured it was working locally as WinRar puts files in temp folder when opened. Doing "ii zipfile" is not working there. This script that you have written works locally as well, but when it is run on Azure it drops error "The 'using' keyword is not supported in this version of the language.". – Kadaj Nov 24 '22 at 13:54
  • 1
    @Kadaj that's odd. I have removed the `using` statements and using the fully qualified names now. That should work – Santiago Squarzon Nov 24 '22 at 13:56
  • Thank you again for trying man, now I get "Method invocation failed because [System.IO.Compression.ZipArchive] does not contain a method named 'new'." I think there might be something wrong with Azure "Powershell" module. I am going to go and investigate. – Kadaj Nov 24 '22 at 14:02
  • 1
    @Kadaj ok, it's a very old version of powershell it seems. older than 5.1 for sure because `::new` was introduced 5.0. What does `$PSVersionTable` returns there? – Santiago Squarzon Nov 24 '22 at 14:03