API to Update Computer Notes and Tags

DustinSVH
Just browsing

API to Update Computer Notes and Tags

I am trying to figure out how to get my powershell script to work but keep getting a 404 error. The PS script shows a form to fill out information I have the data saved in a txt file on the workstation and I want to update the data for the computer in meraki. What is wrong and how could I fix this. Is what I am trying to do possible? I want to update the notes and or tags on a computer.

DustinSVH_0-1742501743214.png

 



Error

Failed to update device: The remote server returned an error: (404) Not Found.

Organization ID: Redacted
Network ID: Redacted
URL: https://api.meraki.com/api/v1/devices/MZ00AVWF
Headers: {
    "X-Cisco-Meraki-API-Key":  "Redacted",
    "Content-Type":  "application/json"
}

Data being pushed to the API:
{
    "serial":  "MZ00AVWF",
    "tags":  [
                 "IS",
                 "Imprivata_Type_1"
             ],
    "name":  "ISWS09",
    "notes":  "Phone Extension: 5166\nDoor Tag: B-105\nOwner: Redacted@Redacted.org"
}



PowerShell Script

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Define the API key and organization ID
$apiKey = 'Redacted'
$orgId = 'Redacted'  # Replace with your actual organization ID

# Function to get the serial number of the computer
function Get-ComputerSerialNumber {
    $wmi = Get-WmiObject -Class Win32_BIOS
    return $wmi.SerialNumber
}

# Function to get the system name of the computer
function Get-ComputerName {
    return $env:COMPUTERNAME
}

# Function to get AD users with sonomavalleyhospital.org email, excluding disabled accounts
function Get-ADUsersWithEmail {
    Import-Module ActiveDirectory
    try {
        $users = Get-ADUser -Filter {EmailAddress -like '*@sonomavalleyhospital.org' -and Enabled -eq $true} -Properties EmailAddress, DisplayName | Where-Object { $_.EmailAddress -ne $null }
        return $users
    } catch {
        Write-Error "Failed to fetch AD users: $_"
        return @()
    }
}

# Function to get all networks in the organization
function Get-MerakiNetworks {
    $url = "https://api.meraki.com/api/v1/organizations/$orgId/networks"
    $headers = @{
        "X-Cisco-Meraki-API-Key" = $apiKey
    }
    try {
        $response = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
        return $response
    } catch {
        Write-Error "Failed to fetch networks: $_"
        return @()
    }
}

# Function to read data from the file
function Read-SystemInfo {
    $filePath = 'C:/Windows/OEM/System_INFO.txt'
    if (Test-Path $filePath) {
        $data = Get-Content $filePath | Out-String
        return $data
    }
    return $null
}

# Function to write data to the file
function Write-SystemInfo {
    param (
        [string]$data
    )
    $filePath = 'C:/Windows/OEM/System_INFO.txt'
    if (Test-Path $filePath) {
        $backupPath = "C:/Windows/OEM/System_INFO_$(Get-Date -Format 'yyyyMMddHHmmss').bak"
        Copy-Item $filePath -Destination $backupPath
    }
    $data | Out-File -FilePath $filePath
}

# Get AD users and Meraki networks
$adUsers = Get-ADUsersWithEmail
$merakiNetworks = Get-MerakiNetworks

# Read existing data from the file
$existingData = Read-SystemInfo
$existingDataHash = @{}
if ($existingData) {
    $existingData -split "`n" | ForEach-Object {
        $parts = $_ -split ": "
        if ($parts.Length -eq 2) {
            $existingDataHash[$parts[0].Trim()] = $parts[1].Trim()
        }
    }
}

# Get the system name and serial number
$systemName = Get-ComputerName
$serialNumber = Get-ComputerSerialNumber

# Create the form
$form = New-Object System.Windows.Forms.Form
$form.Text = "Device Information"
$form.Size = New-Object System.Drawing.Size(400, 700)
$form.StartPosition = "CenterScreen"

# Display system name and serial number
$systemNameLabel = New-Object System.Windows.Forms.Label
$systemNameLabel.Text = "System Name: $systemName"
$systemNameLabel.Location = New-Object System.Drawing.Point(10, 20)
$systemNameLabel.AutoSize = $true
$systemNameLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($systemNameLabel)

$serialNumberLabel = New-Object System.Windows.Forms.Label
$serialNumberLabel.Text = "Serial Number: $serialNumber"
$serialNumberLabel.Location = New-Object System.Drawing.Point(10, 50)
$serialNumberLabel.AutoSize = $true
$serialNumberLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($serialNumberLabel)

# Create dropdown for network selection
$networkLabel = New-Object System.Windows.Forms.Label
$networkLabel.Text = "Select Network:"
$networkLabel.Location = New-Object System.Drawing.Point(10, 80)
$networkLabel.AutoSize = $true
$networkLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($networkLabel)

$networkComboBox = New-Object System.Windows.Forms.ComboBox
$networkComboBox.Location = New-Object System.Drawing.Point(150, 80)
foreach ($network in $merakiNetworks) {
    $networkComboBox.Items.Add($network.name)
}
$form.Controls.Add($networkComboBox)

# Create dropdown for AD user with auto-filter
$userLabel = New-Object System.Windows.Forms.Label
$userLabel.Text = "Assign to AD User:"
$userLabel.Location = New-Object System.Drawing.Point(10, 120)
$userLabel.AutoSize = $true
$userLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($userLabel)

$userComboBox = New-Object System.Windows.Forms.ComboBox
$userComboBox.Location = New-Object System.Drawing.Point(150, 120)
$userComboBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDown
$userComboBox.AutoCompleteMode = [System.Windows.Forms.AutoCompleteMode]::SuggestAppend
$userComboBox.AutoCompleteSource = [System.Windows.Forms.AutoCompleteSource]::ListItems
foreach ($user in $adUsers) {
    $userComboBox.Items.Add($user.DisplayName)
}
$form.Controls.Add($userComboBox)

# Create text fields for notes
$phoneLabel = New-Object System.Windows.Forms.Label
$phoneLabel.Text = "Phone Extension:"
$phoneLabel.Location = New-Object System.Drawing.Point(10, 160)
$phoneLabel.AutoSize = $true
$phoneLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($phoneLabel)

$phoneTextBox = New-Object System.Windows.Forms.TextBox
$phoneTextBox.Location = New-Object System.Drawing.Point(150, 160)
if ($existingDataHash.ContainsKey('Phone Extension')) {
    $phoneTextBox.Text = $existingDataHash['Phone Extension']
}
$form.Controls.Add($phoneTextBox)

$departmentLabel = New-Object System.Windows.Forms.Label
$departmentLabel.Text = "Department:"
$departmentLabel.Location = New-Object System.Drawing.Point(10, 200)
$departmentLabel.AutoSize = $true
$departmentLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($departmentLabel)

$departmentTextBox = New-Object System.Windows.Forms.TextBox
$departmentTextBox.Location = New-Object System.Drawing.Point(150, 200)
if ($existingDataHash.ContainsKey('Department')) {
    $departmentTextBox.Text = $existingDataHash['Department']
}
$form.Controls.Add($departmentTextBox)

$wowLabel = New-Object System.Windows.Forms.Label
$wowLabel.Text = "WOW:"
$wowLabel.Location = New-Object System.Drawing.Point(10, 240)
$wowLabel.AutoSize = $true
$wowLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($wowLabel)

$wowCheckBox = New-Object System.Windows.Forms.CheckBox
$wowCheckBox.Location = New-Object System.Drawing.Point(150, 240)
if ($existingDataHash.ContainsKey('WOW') -and $existingDataHash['WOW'] -eq 'Yes') {
    $wowCheckBox.Checked = $true
}
$form.Controls.Add($wowCheckBox)

$locationLabel = New-Object System.Windows.Forms.Label
$locationLabel.Text = "Location:"
$locationLabel.Location = New-Object System.Drawing.Point(10, 280)
$locationLabel.AutoSize = $true
$locationLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($locationLabel)

$locationTextBox = New-Object System.Windows.Forms.TextBox
$locationTextBox.Location = New-Object System.Drawing.Point(150, 280)
if ($existingDataHash.ContainsKey('Location')) {
    $locationTextBox.Text = $existingDataHash['Location']
}
$form.Controls.Add($locationTextBox)

$doorTagLabel = New-Object System.Windows.Forms.Label
$doorTagLabel.Text = "Door Tag #:"
$doorTagLabel.Location = New-Object System.Drawing.Point(10, 320)
$doorTagLabel.AutoSize = $true
$doorTagLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($doorTagLabel)

$doorTagTextBox = New-Object System.Windows.Forms.TextBox
$doorTagTextBox.Location = New-Object System.Drawing.Point(150, 320)
if ($existingDataHash.ContainsKey('Door Tag #')) {
    $doorTagTextBox.Text = $existingDataHash['Door Tag #']
}
$form.Controls.Add($doorTagTextBox)

$imprivataLabel = New-Object System.Windows.Forms.Label
$imprivataLabel.Text = "Imprivata Type:"
$imprivataLabel.Location = New-Object System.Drawing.Point(10, 360)
$imprivataLabel.AutoSize = $true
$imprivataLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($imprivataLabel)

$imprivataComboBox = New-Object System.Windows.Forms.ComboBox
$imprivataComboBox.Items.AddRange(@("Type 1", "Type 2"))
$imprivataComboBox.Location = New-Object System.Drawing.Point(150, 360)
if ($existingDataHash.ContainsKey('Imprivata Type')) {
    $imprivataComboBox.SelectedItem = $existingDataHash['Imprivata Type']
}
$form.Controls.Add($imprivataComboBox)

$bcaLabel = New-Object System.Windows.Forms.Label
$bcaLabel.Text = "BCA:"
$bcaLabel.Location = New-Object System.Drawing.Point(10, 400)
$bcaLabel.AutoSize = $true
$bcaLabel.MaximumSize = New-Object System.Drawing.Size(380, 0)
$form.Controls.Add($bcaLabel)

$bcaCheckBox = New-Object System.Windows.Forms.CheckBox
$bcaCheckBox.Location = New-Object System.Drawing.Point(150, 400)
if ($existingDataHash.ContainsKey('BCA') -and $existingDataHash['BCA'] -eq 'Yes') {
    $bcaCheckBox.Checked = $true
}
$form.Controls.Add($bcaCheckBox)

# Create OK button
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = "OK"
$okButton.Location = New-Object System.Drawing.Point(150, 440)
$okButton.Add_Click({
    # Ensure the AD user email is fetched before using it
    $adUserEmail = $adUsers | Where-Object { $_.DisplayName -eq $userComboBox.SelectedItem } | Select-Object -ExpandProperty EmailAddress

    # Validate form fields
    if (-not $networkComboBox.SelectedItem) {
        [System.Windows.Forms.MessageBox]::Show("Please select a network.")
        return
    }
    if (-not $userComboBox.SelectedItem) {
        [System.Windows.Forms.MessageBox]::Show("Please assign to an AD user.")
        return
    }

    $selectedNetwork = $merakiNetworks | Where-Object { $_.name -eq $networkComboBox.SelectedItem }
    $tags = @()
    if ($departmentTextBox.Text) {
        $tags += $departmentTextBox.Text
    }
    if ($wowCheckBox.Checked) {
        $tags += "WOW"
    }
    if ($bcaCheckBox.Checked) {
        $tags += "BCA"
    }
    if ($imprivataComboBox.SelectedItem) {
        $tags += "Imprivata_$($imprivataComboBox.SelectedItem -replace ' ', '_')"  # Remove spaces from Imprivata tag
    }

    # Concatenate notes into a single string
    $notes = ""
    if ($phoneTextBox.Text) {
        $notes += "Phone Extension: $($phoneTextBox.Text)`n"
    }
    if ($locationTextBox.Text) {
        $notes += "Location: $($locationTextBox.Text)`n"
    }
    if ($doorTagTextBox.Text) {
        $notes += "Door Tag: $($doorTagTextBox.Text)`n"
    }
    if ($adUserEmail) {
        $notes += "Owner: $adUserEmail"
    }

    # Get the serial number of the computer
    $serialNumber = Get-ComputerSerialNumber

    # Call the Meraki API to update the device notes and tags
    $url = "https://api.meraki.com/api/v1/devices/$serialNumber"
    $headers = @{
        "X-Cisco-Meraki-API-Key" = $apiKey
        "Content-Type" = "application/json"
    }
    $body = @{
        name = $systemName
        notes = $notes
        tags = $tags
        serial = $serialNumber
    } | ConvertTo-Json

    try {
        $response = Invoke-RestMethod -Method Put -Uri $url -Headers $headers -Body $body
        [System.Windows.Forms.MessageBox]::Show("Device updated successfully.")
    } catch {
        $errorMessage = "Failed to update device: $_`n`nOrganization ID: $orgId`nNetwork ID: $($selectedNetwork.id)`nURL: $url`nHeaders: $($headers | ConvertTo-Json)`n`nData being pushed to the API:`n$body"
        $errorForm = New-Object System.Windows.Forms.Form
        $errorForm.Text = "Error"
        $errorForm.Size = New-Object System.Drawing.Size(400, 300)
        $errorForm.StartPosition = "CenterScreen"

        $errorTextBox = New-Object System.Windows.Forms.TextBox
        $errorTextBox.Multiline = $true
        $errorTextBox.ReadOnly = $false
        $errorTextBox.ScrollBars = "Vertical"
        $errorTextBox.Text = $errorMessage
        $errorTextBox.Dock = "Fill"
        $errorForm.Controls.Add($errorTextBox)

        $errorForm.ShowDialog()
    }

    # Write data to the file
    $fileData = @"
System Name: $systemName
Serial Number: $serialNumber
Assigned User: $adUserEmail
Phone Extension: $($phoneTextBox.Text)
Department: $($departmentTextBox.Text)
WOW: $($wowCheckBox.Checked)
Location: $($locationTextBox.Text)
Door Tag #: $($doorTagTextBox.Text)
Imprivata Type: $($imprivataComboBox.SelectedItem)
BCA: $($bcaCheckBox.Checked)
"@
    Write-SystemInfo -data $fileData

    $form.Close()
})
$form.Controls.Add($okButton)

# Show the form
$form.ShowDialog()

 

1 Reply 1
sungod
Kind of a big deal
Kind of a big deal

This will not work, the endpoint is for Meraki devices, not arbitrary client devices such as PCs etc.

URL: https://api.meraki.com/api/v1/devices/MZ00AVWF

 

There is no Meraki device with that serial number, so you get a 404 response.

 

If the PC/whatever is managed by System Manager, there're a bunch of endpoints to get/set various info, for instance https://developer.cisco.com/meraki/api-v1/modify-network-sm-devices-tags/

 

I don't recall seeing an endpoint that updates notes/tags info for non-managed clients, if there were one I'd expect it to be based on the Dashboard generated client ID or client MAC, not the client's native serial number.

Get notified when there are additional replies to this discussion.