I am using 'OpenTelemetry.Contrib.Preview' package to attach ILogger standalone logs as Activity Events (aka Trace events) to co-relate the logs with trace & show at a single place like Jaeger. As part of the documentation if we want to attach the logs we need to call 'AttachLogsToActivityEvent' along with setting 'IncludeFormattedMessage' to true during the log setup at startup.cs. While I see the logs are successfully being attached to the activity but only the default 3 properties CategoryName, LogLevel & EventId are being added and the actual 'FormattedMessage' is not getting logged even though I have it enabled. Searched in their github repo where I found this below issue, but the same is not working here: https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/134
Any idea what's missing?
csproj file
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0-*" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="7.0.0" />
<PackageReference Include="OpenTelemetry.Contrib.Preview" Version="1.0.0-beta2" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.HttpListener" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.4.0-rc.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9.10" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9.10" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.1.0-beta.2" />
</ItemGroup>
</Project>
Program.cs file (there are bunch of lines for swagger/versioning testing)
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddProblemDetails();
services.AddEndpointsApiExplorer();
services.AddApiVersioning(options => options.ReportApiVersions = true)
.AddApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
})
.EnableApiVersionBinding();
services.AddSwaggerGen();
builder.Services.Configure<AspNetCoreInstrumentationOptions>(options => options.RecordException = true);
var resource = ResourceBuilder.CreateDefault().AddService("MyService");
builder.Services
.AddOpenTelemetry()
.ConfigureResource(ConfigureResource)
.WithTracing(o =>
{
o.SetSampler(new TraceIdRatioBasedSampler(1.0))
.AddSource("MyService")
.AddAspNetCoreInstrumentation(option => option.RecordException = true)
.AddHttpClientInstrumentation();
o.AddConsoleExporter(o => o.Targets = ConsoleExporterOutputTargets.Console);
}).StartWithHost();
builder.Logging.ClearProviders();
builder.Logging.Configure(o => o.ActivityTrackingOptions = ActivityTrackingOptions.SpanId | ActivityTrackingOptions.TraceId | ActivityTrackingOptions.ParentId);
builder.Logging.AddFilter<OpenTelemetryLoggerProvider>("*", LogLevel.Information);
builder.Logging
.AddOpenTelemetry(loggerOptions =>
{
loggerOptions.SetResourceBuilder(resource);
loggerOptions.IncludeFormattedMessage = true;
loggerOptions.IncludeScopes = true;
loggerOptions.ParseStateValues = true;
loggerOptions.AddConsoleExporter();
loggerOptions.AttachLogsToActivityEvent();
});
var app = builder.Build();
var common = app.NewVersionedApi("Common");
var commonV1 = common.MapGroup("/api/v{version:apiVersion}/common").HasApiVersion(1.0);
commonV1.MapGet("/", (ILogger<Program> logger) =>
{
logger.LogInformation("Calling hello world api!");
return Results.Ok("Hello World common v1!");
});
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
});
app.Run();
static void ConfigureResource(ResourceBuilder r) => r.AddService(
serviceName: "MyService",
serviceVersion: "1.0.0",
serviceInstanceId: Environment.MachineName);
Once I run the above program & execute the endpoint, I see the activity is getting logged with the additional 'log' even that I added as part of the endpoint execution, but it doesn't include the actual 'FromattedMessage' property though, like an example below...
I have opened the same issue in github too: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4052
If I go to Project->MyApp->Properties->Settings and enter Name like WhichApp of type string and enter the Scope as Application and Value of PCB. When I show the value in
my main form using MessageBox.Show(Properties.Settings.Default.WhichApp); I see a message box showing "PCB".
But if I try to set the property using MSBuild.exe -property:WhichApp=SUB when I view the property value I still see "PCB".
How can I call the MSBuild.exe compiler tool to set the property at build time?
I tried used External Tools in Visual Studio..
Title: Set Property
Command: C:\Program Files(x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe
Arguments: -property:WhichApp=SUB
Initial Directory: $(ProjectDir)
---- WHAT I'M TRY TO ACCOMPLISH IN LOAD() METHOD OF MAIN FORM ----
if (Properties.Settings.Default.PCAppConfig == "PCB")
{
// create new PCB form
// PCBForm.ShowDialog();
}
else if (Properties.Settings.Default.PCAppConfig == "SUB")
{
// create new SUB form
// SUBForm.ShowDialog();
}
--- In solution by jtijn, I tried this to add another property called VersionNum but fails... ---
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<VersionNum Condition="'$(VersionNum)' == ''">1.0.0.66</VersionNum>
<!--The source code.-->
<TheSourceCode>internal static class PcAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B%0A }internal static class VersionNum{ public static readonly string value = "$(VersionNum)"%3B</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
</Target>
--- Doesn't compile when values are changed, Fails to create setup files ...
My arguments to MSBuild.exe
SelectApp.csproj.user /p:PcAppConfig=SUB /p:VersionNum=1.0.0.67
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Form1.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<VersionNum Condition="'$(VersionNum)' == ''">1.0.0.66</VersionNum>
<!--The source code.-->
<TheSourceCode>internal static class PCAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B }%0Ainternal static class VersionNum{ public static readonly string value = "$(VersionNum)"%3B }</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
<ItemGroup>
<Compile Update="PcAppConfig.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
</Target>
<Target Name="AfterCompile">
<Exec Command=""$(ProgramFiles)\Microsoft Visual Studio\2019\Professional\Common7\IDE\devenv.com" C:\DummyApps\SelectApp\PCBSetup\PCBSetup.vdproj /build "Debug|AnyCPU""/>
</Target>
</Project>
You can have the project generate C# source code based on an msbuild property. This target goes at the end in the project file:
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<!--The source code.-->
<TheSourceCode>internal static class PcAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B }</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
</Target>
The resulting source file needs to be added to your project, can do this in VS after building once or else add this line along with the other Compile items:
<Compile Include="PcAppConfig.cs" />
Now in your code you can just use PcAppConfig.value.
To change the value build like
msbuild my.csproj /p:PcAppConfig=SUB
I am trying to get my app to automatically install an apk. This works fine for api<24. But for 24, it is failing. Android has implemented extra security:
For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app. If an intent containing a file URI leaves your app, the app fails with a FileUriExposedException exception.
So I tried this:
Uri myuri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
myuri = Uri.parse("file://"+outapk);
} else {
File o = new File(outapk);
myuri = FileProvider.getUriForFile(con, con.getApplicationContext().getPackageName() + ".provider", o);
}
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(myuri,"application/vnd.android.package-archive");
con.startActivity(promptInstall);
but get a fatal exception:
com.android.packageinstaller "Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{b42ee8a 6826:com.android.packageinstaller/u0a15} (pid=6826, uid=10015) that is not exported from uid 10066".
I have export=true in my manifest.
The problem seems to be that packageinstaller cannot use a content:// uri.
Are there any ways to allow an app to progammatically install an apk with api24?
Are there any ways to allow an app to progammatically install an apk with api24?
Add addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) to your promptInstall setup, to grant read access to the content.
I have export=true in my manifest.
Not on your FileProvider, as that would cause your app to crash.
The problem seems to be that packageinstaller cannot use a content:// uri.
No, the problem is that you did not grant permission to the package installer to read from that Uri. Had the package installer been unable to use a content scheme, you would have gotten an ActivityNotFoundException.
Note, though, that it is only with Android 7.0 that the package installer starts supporting content. Earlier versions of Android have to use file.
For Oreo, Add permission in AndroidManifast (Otherwise it just silently fails)
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
now add to you'r Manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
in xml directory add...
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." /></paths>
then use these codes where you want.
File directory = Environment.getExternalStoragePublicDirectory("myapp_folder");
File file = new File(directory, "myapp.apk"); // assume refers to "sdcard/myapp_folder/myapp.apk"
Uri fileUri = Uri.fromFile(file); //for Build.VERSION.SDK_INT <= 24
if (Build.VERSION.SDK_INT >= 24) {
fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
}
Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //dont forget add this line
context.startActivity(intent);
}
For Oreo,
Add permission in AndroidManifast
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
Here is the solution I have found
val newFile = File(dirPath, "$fileNameWithoutExtn.apk")
var fileUri = Uri.fromFile(newFile)
//use the fileProvider to get the downloaded from sdcard
if (Build.VERSION.SDK_INT >= 24) {
fileUri = FileProvider.getUriForFile(this#SettingAcitivity, applicationContext.packageName + ".provider", newFile)
val intent=Intent(Intent.ACTION_VIEW)
intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}else{
newFile.setReadable(true, false)
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
and write in manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
and also set the permission
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
and in xml folder paths will be
<paths>
<external-path
name="external_files"
path="." />
</paths>
Add file in res/xml -> provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Add this code in AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.provider" <-- change this with your package name
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
run this code for install your app or open
public void installApk(String file) {
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider",new File(file));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
}
simply do the following steps:
1- add the following permission to manifest:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
2- Add provider to manifest (as child as application tag):
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="tatcomputer.ir.libraryapp.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
3- Add paths.xml to xml directory:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="." />
<external-files-path name="external_files" path="." />
<external-path name="external_files" path="." />
<cache-path name="cached_files" path="." />
<external-cache-path name="cached_files" path="." />
</paths>
4- show install apk page using following code (note that in my case apk is in root of my phone named tmp.apk:
String root = Environment.getExternalStorageDirectory().toString();
Uri fileUri24 = FileProvider.getUriForFile(App.applicationContext, BuildConfig.APPLICATION_ID + ".provider", new File(root + "/tmp.apk"));
Uri fileUri = Uri.fromFile(new File(root + "/tmp.apk"));
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24)
intent.setDataAndType(fileUri24, "application/vnd.android.package-archive");
else intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
App.applicationContext.startActivity(intent);
Intall Apk automatically
Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent1.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" +filename)), "application/vnd.android.package-archive");
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
In my case on android 8.0 problem was in
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
More detailed how to get this permission Exception 'open failed: EACCES (Permission denied)' on Android
Update in 2022
You can't use internal storage to install an APK. You have to use External Storage instead.
And this is for people looking at how to install step-by-step APK on a remote Android device. Also, the tutorial has a NodeJS Server implementation.
Remote install for Android app using APK
I am using the NuGet.targets file from NuGet to automatically download NuGet.exe and then restore the packets in my project.
This was working well, however at work we have a proxy and this method was failing due to a (407) Proxy Authentication Required Exception. I modified the targets file to use the proxy details and although this method works in an application it does not work in the MSBuild Task, the code is identical.
If I hardcode the proxy and my login details it works, when I build my solution NuGet.exe is downloaded and the packages are restored correctly. The problem only appears to be the authentication in the MSBuild task, I have absolutely no idea why. I have attached my modified code.
If anyone can help I'd appreciate it. Thanks
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputFilename ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Net" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try
{
OutputFilename = Path.GetFullPath(OutputFilename);
Log.LogMessage("Downloading latest version of NuGet.exe...");
using(WebClient webClient = new WebClient())
{
webClient.UseDefaultCredentials = true;
webClient.Proxy = WebRequest.GetSystemWebProxy();
webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
}
catch (Exception ex)
{
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
I am using MSBuild.
I am getting the value of the Person_1 through the $(Person_1). How can I get the value of the Name subelement of Person_2? I need the syntax.
<PropertyGroup>
<Person_1>Bob</Person_1>
<Person_2>
<Name>Bob</Name>
</Person_2>
</PropertyGroup>
RE: https://msdn.microsoft.com/en-us/library/ms171458.aspx
A property that contains XML is simply that. You cannot access parts of the content just because it is XML. To understand this do the following;
<PropertyGroup>
<MyProperty>
<PropertyContentXML>
<InnerXML1>Blablabla</InnerXML1>
<InnerXML2>More blablabla</InnerXML2>
</PropertyContentXML>
</MyProperty>
</PropertyGroup>
<Target Name="Build">
<Message Text="$(MyProperty)" />
</Target>
The output of this will be:
<PropertyContentXML xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<InnerXML1>Blablabla</InnerXML1>
<InnerXML2>More blablabla</InnerXML2>
</PropertyContentXML>
You are mixing Properties and ItemGroups.
Properties are simple named values, ItemGroups are items with an identity and with properties. You can not use both in the same way.
Properties are defined as :
<PropertyGroup>
<name>value</name>
</Propertygroup>
and are accessed by using the $(name) syntax.
Item groups are defined as:
<ItemGroup>
<Item Include="item1">
<ItemPropery>value</ItemProperty>
</Item>
</ItemGroup>
and are accessed by using this syntax: %(Item.ItemProperty).
See also this reference for the 'intuitive' syntax
You'll need something advanced, like an inline task:
<UsingTask TaskName="TransformXmlToItem"
TaskFactory="CodeTaskFactory"
AssemblyName="Microsoft.Build.Tasks.Core">
<ParameterGroup>
<Xml Required="true"/>
<Elements ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true"/>
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
using (var xr = new XmlTextReader(Xml, XmlNodeType.Element,
new XmlParserContext(null, null, null, XmlSpace.Default))) {
xr.Namespaces = false;
xr.MoveToContent();
var items = new List<ITaskItem>();
while (!xr.EOF) {
if (xr.NodeType == XmlNodeType.Element) {
var item = new TaskItem(xr.Name);
var text = xr.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(text)) {
item.SetMetadata("text", text);
}
}
xr.Read();
}
Elements = items.ToArray();
}
]]>
</Code>
</Task>
The task reads the XML elements and creates items from it. The text is transformed into metadata.
You can then write a task like this:
<Target Name="DeconstructPropertyXml">
<TransformXmlToItem Xml="$(Person_2)">
<Output TaskParameter="Elements" ItemName="Person_2I"/>
</TransformXmlToItem>
<Message Text="%(Person_2I.Identity) = %(Person_2I.text)" Importance="high"/>
</Target>
Which should just output Name = Bob.
The same way you could add additional metadata from attributes, etc.