|  | function Get-GitlabIssues { | 
        
          |  | [CmdletBinding()] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $ContainerPath) | 
        
          |  | Begin { | 
        
          |  | $issues = $null; | 
        
          |  | $result = $null; | 
        
          |  | $page = 1; | 
        
          |  | Write-Progress -Activity "Get-GitlabIssues" -Status "0 Complete:" -PercentComplete 0; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | $total = (Invoke-RestMethod -Method Get -Uri "https://gitlab.com/api/v4/$ContainerPath/issues_statistics" -Headers @{ 'Authorization' = "Bearer $AccessToken" }).statistics.counts.all; | 
        
          |  | while($result -ne 0) { | 
        
          |  | $result = Invoke-RestMethod -Method Get -Uri "https://gitlab.com/api/v4/$ContainerPath/issues?page=$page&per_page=100" -Headers @{ 'Authorization' = "Bearer $AccessToken" } | 
        
          |  | $issues += $result; | 
        
          |  | if($issues.Count  -eq 0) { | 
        
          |  | $count = 1; | 
        
          |  | } else { | 
        
          |  | $count = $issues.Count; | 
        
          |  | } | 
        
          |  | $percent = $count/$total*100; | 
        
          |  | $page ++; | 
        
          |  | Write-Progress -Activity "Get-GitlabIssues" -Status "$percent% Complete:" -PercentComplete $percent; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | return $issues; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Get-GitlabComments { | 
        
          |  | [CmdletBinding()] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [parameter(Mandatory=$true, ValueFromPipeline)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $NotesLink) | 
        
          |  | Begin { | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | try{ | 
        
          |  | Invoke-RestMethod -Method Get -Uri "$NotesLink`?page=$page&per_page=100" -Headers @{ 'Authorization' = "Bearer $AccessToken" }; | 
        
          |  | } catch { | 
        
          |  | $issueIid = $NotesLink.Split("/") | Select-Object -Last 2 | Select-Object -First 1 | 
        
          |  | Write-Error -Message "Failed to get comments for issue: $issueIid" -ErrorId 500 -TargetObject $issueIid -Category InvalidResult; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Get-GitLabAttachment { | 
        
          |  | [CmdletBinding( | 
        
          |  | SupportsShouldProcess, | 
        
          |  | ConfirmImpact="Medium" | 
        
          |  | )] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [parameter(Mandatory)] | 
        
          |  | [System.String] | 
        
          |  | $Root, | 
        
          |  | [parameter(Mandatory)] | 
        
          |  | [System.String] | 
        
          |  | $Issue, | 
        
          |  | [parameter(Mandatory)] | 
        
          |  | [System.String] | 
        
          |  | $File) | 
        
          |  | Begin { | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | $path = Join-Path -Path $Root -ChildPath $Issue | Join-Path -ChildPath "Attachments"; | 
        
          |  | New-Item -Path $path -Type Directory -Force | Out-Null; | 
        
          |  | $path = $path | Join-Path -ChildPath ("~"+([string]::Join("~", ($File.Split("/") | Select-Object -Last 3)))); | 
        
          |  | if(-Not (Test-Path -Path $path)) { | 
        
          |  | Try { | 
        
          |  | Invoke-WebRequest -OutFile $path -Uri $File -Headers @{ 'Authorization' = "Bearer $AccessToken" }; | 
        
          |  | } Catch { | 
        
          |  | Write-Error -Message "Failed to get attachment: $File for issue: $Issue" -ErrorId 500 -TargetObject $Issue -Category InvalidResult; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Add-DevopsWorkItem { | 
        
          |  | [CmdletBinding( | 
        
          |  | SupportsShouldProcess=$true, | 
        
          |  | ConfirmImpact="Medium" | 
        
          |  | )] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Organization, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Project, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | [ValidateSet("Epic", "Feature", "User Story", "Bug", "Issue", "Task")] | 
        
          |  | $Type, | 
        
          |  | [System.Int32] | 
        
          |  | $Id, | 
        
          |  | [parameter(Mandatory=$true,ValueFromPipeline)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Data, | 
        
          |  | [Switch] | 
        
          |  | $Test) | 
        
          |  | Begin { | 
        
          |  | $basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))) | 
        
          |  | $uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/workitems/`$$($Type)?api-version=5.1&bypassRules=true&suppressNotifications=true"; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | if ($PSCmdlet.ShouldProcess($Data)) { | 
        
          |  | Try { | 
        
          |  | if($Test) { | 
        
          |  | Invoke-RestMethod -Method Patch -Uri $uri+"&validateOnly=true" -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json-patch+json' -Body $Data | 
        
          |  | } else { | 
        
          |  | Invoke-RestMethod -Method Patch -Uri $uri -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json-patch+json' -Body $Data | 
        
          |  | } | 
        
          |  | } Catch { | 
        
          |  | Write-Error -Message "Failed to add issue for issue: $($Id)" -ErrorId 500 -TargetObject $Data -Category InvalidResult; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Add-DevopsComment { | 
        
          |  | [CmdletBinding( | 
        
          |  | SupportsShouldProcess=$true, | 
        
          |  | ConfirmImpact="Medium" | 
        
          |  | )] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Organization, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Project, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.Int32] | 
        
          |  | $Id, | 
        
          |  | [parameter(Mandatory=$true,ValueFromPipeline)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Data) | 
        
          |  | Begin { | 
        
          |  | $basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))) | 
        
          |  | $uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/workItems/$Id/comments?api-version=5.1-preview.3"; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | if ($PSCmdlet.ShouldProcess($Data)) { | 
        
          |  | Try{ | 
        
          |  | Invoke-RestMethod -Method Post -Uri $uri -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json' -Body $Data | 
        
          |  | } Catch { | 
        
          |  | Write-Error -Message "Failed to add comments for issue: $($Id)" -ErrorId 500 -TargetObject $Id -Category InvalidResult; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Add-DevopsAttachment { | 
        
          |  | [CmdletBinding( | 
        
          |  | SupportsShouldProcess=$true, | 
        
          |  | ConfirmImpact="Medium" | 
        
          |  | )] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $AccessToken, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Organization, | 
        
          |  | [ValidateNotNullOrEmpty()] | 
        
          |  | [System.String] | 
        
          |  | $Project, | 
        
          |  | [parameter(Mandatory=$true,ValueFromPipeline)] | 
        
          |  | [System.IO.FileInfo] | 
        
          |  | $File) | 
        
          |  | Begin { | 
        
          |  | $basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))); | 
        
          |  | $uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/attachments?api-version=5.0"; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | if ($PSCmdlet.ShouldProcess($FileName)) { | 
        
          |  | Try{ | 
        
          |  | $image = [System.IO.File]::ReadAllBytes($File.FullName); | 
        
          |  | Invoke-RestMethod -Uri $uri+"&fileName=$([System.Web.HttpUtility]::UrlEncode($File.Name))" -Method Post -Headers @{ Authorization=("Basic {0}" -f $basicAuth) } -ContentType application/json -Body $image; | 
        
          |  | } Catch { | 
        
          |  | Write-Error -Message "Failed to add attachments for issue: $($File.FullName)" -ErrorId 500 -TargetObject $File -Category InvalidResult; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function Convert-IssueToWorkItem { | 
        
          |  | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] | 
        
          |  | [CmdletBinding()] | 
        
          |  | [OutputType([string])] | 
        
          |  | param ( | 
        
          |  | [parameter(Mandatory=$true)] | 
        
          |  | $RootIterationPath, | 
        
          |  | [parameter(Mandatory=$true,ValueFromPipeline)] | 
        
          |  | $Issue | 
        
          |  | ) | 
        
          |  | Begin { | 
        
          |  | Import-Module MarkdownToHtml -Force; | 
        
          |  | $userMap = @{ | 
        
          |  | #https://gitlab.com/api/v4/groups/mygroupid/members GitLabUserUserId="AzureDevOpsDisplayName", | 
        
          |  | 123 ="John Doe"; | 
        
          |  | }; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | if([String]::IsNullOrWhiteSpace($Issue.description)) { | 
        
          |  | $content = "`" `""; | 
        
          |  | } else { | 
        
          |  | $content = ((Convert-MarkdownToHTMLFragment -Markdown ($Issue.description | Format-BadCharacters)).HtmlFragment).Replace("₹", "₹").Replace("'", "'") | ConvertTo-Json; | 
        
          |  | } | 
        
          |  | $title = [System.Web.HttpUtility]::HtmlEncode(($Issue.title | Format-BadCharacters)); | 
        
          |  | if($title.Length -gt 255){$title = $title.Substring(0,247);} | 
        
          |  | $author = $($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.author.id}).Value; | 
        
          |  | $assignee = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.assignee.id}).Value; | 
        
          |  | $closedby = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.closed_by.id}).Value; | 
        
          |  | $tags = [String]::Join(";", $Issue.labels+$Issue.state); | 
        
          |  | if($Issue.milestone) { | 
        
          |  | $iteration = $RootIterationPath+"\\"+$Issue.milestone.title.Replace("?", "").Replace("&", "and"); | 
        
          |  | } else { | 
        
          |  | $iteration = $RootIterationPath; | 
        
          |  | } | 
        
          |  | if($null -ne $Issue.assignee){ | 
        
          |  | $user = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.assignee.id}).Value; | 
        
          |  | $assignee = ", | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.AssignedTo`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($user)`" | 
        
          |  | }" | 
        
          |  | } else { | 
        
          |  | $assignee = ""; | 
        
          |  | } | 
        
          |  | $type = @("User Story", "Bug")[$Issue.labels.Contains("Bug")]; | 
        
          |  | if($type -eq "Bug") { | 
        
          |  | $body = ", | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/Microsoft.VSTS.TCM.ReproSteps`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": $content | 
        
          |  | }" | 
        
          |  | } else { | 
        
          |  | $body = ", | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.Description`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": $content | 
        
          |  | }" | 
        
          |  | } | 
        
          |  | if($Issue.state -eq "closed"){ | 
        
          |  | $closed = ", | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/Microsoft.VSTS.Common.ClosedBy`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($closedby)`" | 
        
          |  | }, | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/Microsoft.VSTS.Common.ClosedDate`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($Issue.closed_at)`" | 
        
          |  | }"; | 
        
          |  | } else { | 
        
          |  | $closed = ""; | 
        
          |  | } | 
        
          |  | return "[ | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.IterationPath`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($iteration)`" | 
        
          |  | }, | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.Title`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($Issue.iid): $($title)`" | 
        
          |  | }$($body), | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.State`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$(@{"opened"="New"; "closed" = "New"}[$Issue.state])`" | 
        
          |  | }, | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.Tags`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($tags)`" | 
        
          |  | }, | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.CreatedBy`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($author)`" | 
        
          |  | }, | 
        
          |  | { | 
        
          |  | `"op`": `"add`", | 
        
          |  | `"path`": `"/fields/System.CreatedDate`", | 
        
          |  | `"from`": null, | 
        
          |  | `"value`": `"$($Issue.created_at)`" | 
        
          |  | }$($assignee)$($closed) | 
        
          |  | ]"; | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function Convert-NoteToComment { | 
        
          |  | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] | 
        
          |  | [CmdletBinding()] | 
        
          |  | [OutputType([string])] | 
        
          |  | param( | 
        
          |  | [parameter(Mandatory,ValueFromPipeline)] | 
        
          |  | $Note | 
        
          |  | ) | 
        
          |  | Begin { | 
        
          |  | Import-Module MarkdownToHtml -Force; | 
        
          |  | } | 
        
          |  | Process { | 
        
          |  | $header = "<p><b>$($Note.created_at.ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss')) by $($Note.author.name)</b></p>"; | 
        
          |  | if([String]::IsNullOrWhiteSpace($Note.body)) { | 
        
          |  | $content = "`" `""; | 
        
          |  | } else { | 
        
          |  | $content = ($header + (Convert-MarkdownToHTMLFragment -Markdown ($Note.body | Format-BadCharacters)).HtmlFragment).Replace("₹", "₹") | ConvertTo-Json; | 
        
          |  | } | 
        
          |  | return "{ `"text`": $content }"; | 
        
          |  | } | 
        
          |  | End { | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function Format-BadCharacters { | 
        
          |  | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] | 
        
          |  | [CmdletBinding()] | 
        
          |  | [OutputType([string])] | 
        
          |  | param ( | 
        
          |  | [parameter( | 
        
          |  | Mandatory, | 
        
          |  | ValueFromPipeline, | 
        
          |  | ValueFromPipelineByPropertyName, | 
        
          |  | Position = 0)] | 
        
          |  | [string] $Input | 
        
          |  | ) | 
        
          |  | Process { | 
        
          |  | return $Input.Replace("`“", "`"").Replace("`”", "`"").Replace(" ", " ").Replace("ä", "a").Replace("ë", "e"); | 
        
          |  | } | 
        
          |  | } |