DataWedge is a hugely popular way to access Zebra device hardware providing a ‘zero-code’ way to simply and easily acquire barcode data, use SimulScan capabilities or use the in-built card reader, where supported. DataWedge is often used in preference to Zebra’s native SDKs for Java and Xamarin (EMDK) because it offers a simpler interface that ‘just works’ but this ease of use comes at a cost of losing more fine-grained control of the hardware.  The DataWedge Intent API attempts to redress this by enabling user applications to control, modify and query the current DataWedge configuration and operation.


What is DataWedge?

For a more comprehensive explanation of DataWedge please refer to the documentation available on Zebra’s techdocs site.  For the purposes of this blog the salient points are:

  • DataWedge is a service running on all Zebra Android devices which controls the device scanner and provides access to other value-adds such as SimulScan and the ‘Data Capture Plus (DCP)’ panel.
    • DataWedge as a product has been around for over a decade starting life as a post-install application on Zebra’s WM/CE products – it is not going away!
  • DataWedge is configured with Profiles which encapsulate how it should behave in any given situation – profiles define an input – processing – output pipeline i.e. how to capture data, perform any processing on that data and finally how to output that data to the application.
    • Traditionally profiles are defined using the device UI or using the EMDK but must be created prior to use by the application.
    • Any application can retrieve data from DataWedge regardless of the underlying application technology, we have customers using DataWedge with native applications (Java / Xamarin), hybrid applications (e.g. Cordova) and pure web applications running in Chrome or Enterprise Browser.
    • The DataWedge service listens for and responds to broadcast intents with certain actions – this is what comprises the DataWedge API being discussed here.
    • One method of outputting data to the application is via the ‘Intent’ plugin, this is separate to and distinct from the DataWedge API which also uses Android intents.


The above diagram shows how DataWedge interacts with a calling application via intents


The DataWedge API overview

The DataWedge API was introduced to bridge the gap between the highly configurable native SDKs (EMDK) and the ease of use that the DataWedge service provides.


So far there have been three versions of the API released, starting with DataWedge 6.0, then enhanced in DataWedge 6.2 and most recently made more functional in DataWedge 6.3.  Since each iteration of the API introduced new capabilities and even changed the format required for some of the features it is important to understand which version(s) of DataWedge you are running in your device deployment.  You can determine the DW version from the DataWedge application UI as explained here and 6.3 also exposes an API to programmatically determine the version.


Upgrading DataWedge can only be performed via a full operating system update from Android Lollipop onwards.  Pre-Lollipop, DataWedge can be upgraded via the “Device Runtime Deployment” application provided as part of EMDK.  You should therefore be aware of DataWedge API changes when upgrading your devices and adding new devices to any deployment.


DataWedge 6.0

Provided rudimentary capabilities to switch profiles and initiate / disable scanning.  This was useful if you had pre-defined the profile(s) your application would require and enabled your application to control when to scan e.g. you could have a software button in your application that behaved the same as the device hardware trigger.


DataWedge 6.2

Enabled some basic profile management: your application is now able to delete, clone and rename existing profiles as well as retrieve the currently active profile. This was useful if you were configuring devices via an application and provided an alternative to the DataWedge service UI or EMDK to manage profiles but since you could not create new profiles, the usefulness at the time was limited.


A new format was introduced to the Intent API allowing you to invoke multiple commands from the same intent, i.e. you could clone and rename a profile in a single call.


DataWedge 6.3

Several capabilities were introduced in response to customer feedback including:

  • A version API
  • Receive notifications when the scanner state or profile changes.  Useful if you want to only allow the user to initiate a soft scan when the scanner hardware is ready or if you want to know when the correct profile is in effect.
  • Create profiles and modify the contents of profiles.  Not only does this make profile set-up feasible through the API but it also enables a slew of other use cases such as enabling / disabling decoders at runtime (e.g. only enable EAN13 on a single app screen) or configuring scanner parameters at runtime (e.g. enabling picklist mode under certain conditions)
  • The format of the APIs introduced in 6.0 was updated to allow multiple invocations in the same intent e.g. to enable scanning and then initiate a soft scan with the same call.  Whilst this meant changing the API, the original 6.0 API remains supported for backwards compatibility.


Sample Application

To help illustrate how to call the DataWedge APIs I have created a sample native app on my personal GitHub here:, called ‘DataWedge API Exerciser’.  Note that this application is purely for illustrative purposes and is provided without any kind of support from Zebra.



The API exerciser demonstrates both the DataWedge APIs (6.0, 6.2 and 6.3) as well as the various ways to receive scan data from the Intent output plugin: via StartActivity, SendBroadcast or StartService.  In order to retrieve scan data via intents you will need to configure device to listen for intents with action com.zebra.dwapiexerciser.ACTION, as explained in the ReadMe file.  Note that the test for the 6.3 CreateProfile API will create such a profile automatically.


The user interface of the API exerciser is hopefully fairly self-explanatory (if a bit ugly!); note you may find it useful to have your device connected to adb and monitor DataWedge logcat whilst using the app to observe DataWedge logs:


$ adb logcat -s DWAPI


Remember to enable logging in DataWedge before use.


Things to be aware of / Usage scenarios

Whilst putting together the API exerciser application above I came across a number of scenarios which it is worth bearing in mind.


Note that the following conditions exist at the time of writing (June 2017) and may be addressed in future updates to the DataWedge API


The API changed between 6.0 and 6.3 for some commands

As previously mentioned, the API changed between versions 6.0 and 6.3 for the following commands:


In and of itself this is not a constraint since new devices are fully backwards compatible and the 6.0 APIs will continue to work regardless of the DataWedge version.  The difficulty comes where you want to target the lowest common denominator of your devices so if any of your devices do not support 6.3+, you will need to ensure you target the older APIs - be careful since the documentation will always default to the latest versions so use the documentation links above and refer to the API exerciser app for examples of using each API.


Another complication with this is the format of EnumerateScanners has changed from a String array to a bundle array in 6.3 with the bundle containing amongst other information the scanner ID.  This scanner ID is required in any calls to the 6.3 API ‘SetConfig’, therefore strictly if you want to use the SetConfig API you first need to determine the scanner ID via the 6.3 EnumerateScanners – notice in the DW API exerciser I assume the scanner IDs are contiguous but this is not best programming practise for a production application.


API return values are always via implicit broadcast

Although the Intent output Plugin supports sending intents to the application via StartActivity, StartService or SendBroadcast, returned data from the DataWedge API only supports broadcast intents.  As well as being less flexible and requiring the receiving application to have a broadcast receiver it means that any application on the device can listen to the return values being passed; there should not be any security concerns since the DataWedge API is available for any application to call but it is worth bearing in mind.


Whilst not yet supported on any Zebra devices, Android ‘O’ adds restrictions to how broadcast intents can be received.  The link is here but if that link gets moved, search for ‘Android O broadcast limitations’.  You cannot register for implicit broadcast receivers in your application manifest but must dynamically register and unregister to receive broadcasts at runtime, as follows:


//  onResume IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_RESULT_DATAWEDGE_FROM_6_2);//  DW 6.2 filter.addAction(ACTION_RESULT_NOTIFICATION);      //  DW 6.3 for notifications filter.addCategory(Intent.CATEGORY_DEFAULT);     registeReceiver(myBroadcastReceiver, filter);  //  onPause unregisterReceiver(myBroadcastReceiver);


Given this change will be coming in future Android versions it would not make sense to develop new applications which declare broadcast intents in the manifest, in my opinion.


You cannot associate the callback with the caller

You make a query to the DataWedge API by sending a broadcast intent and you get a reply back via a broadcast intent.  Take for example enumerate scanners (6.3), you send an intent with:


Action: com.symbol.datawedge.api.NOTIFICATION_ACTION Extra: com.symbol.datawedge.api.ENUMERATE_SCANNERS


And you receive a broadcast intent shortly after with:


Action: com.symbol.datawedge.api.RESULT_ACTION Extra: com.symbol.datawedge.api.RESULT_ENUMERATE_SCANNERS


How do you know that the received intent is associated with your original call?  You do not really know, especially if you are sending the intent from multiple points in your application – indeed since these are broadcast intents, the request may conceivably have originated from outside of your application.


I had this experience when creating the API exerciser - I wanted to have a drop-down list of available scanners, populated in onCreate() and I also wanted to have a button the user could press which would show a toast with the available scanners, but I did not want the toast to show every time onCreate() is called.  You could work around this with some class variable to track the state of the requests but I ended up changing my application logic.  This is not particular to enumerate scanners, anything returned from the DataWedge API will work this way including the active profile, version information etc..


Your broadcast receiver requires a filter on the default category

As stated above, all data returned to the application by the DataWedge API is done via Broadcast intents.


In the 6.0 APIs, it was sufficient to just declare an intent filter for the required action (only enumerate scanners was supported), dynamically register a broadcast receiver and you would receive the broadcast data.


From 6.2 upwards, additional attributes are returned via broadcast e.g. active profile, version info, profiles list etc.  These 6.2 returned broadcasts are configured to have the CATEGORY_DEFAULT category.  This is best practice but is worth noting that if you did not previously add CATEGORY_DEFAULT to your intent filter for 6.0, it will need to be added to work with 6.2.


There are no return values from any commands

There is no feedback when any of these APIs are called, for example when calling SwitchProfile your application has no way of knowing:

  1. That the application has finished switching and the new profile is ready to go
  2. That some error did not occur during the switch.


This is a condition which will likely be addressed in the future.


For debugging purposes, you should use logcat to observe any error messages coming from the DataWedge service:


$ adb logcat -s DWAPI


D/DWAPI  ( 1157): switchToProfile: Profile0 (default) D/DWAPI  ( 1157): Profile id = 1, current profile id = 15 D/DWAPI  ( 1157): Profile 1 found, loading… D/DWAPI  ( 1157): Current profile is now ‘Profile 0 (default)’.


Also, be aware that any call to the RestoreConfiguration API will disable logging so you will need to re-enable it via the DataWedge application UI (unfortunately there is no programmatic way to enable logging at this time)


Compounding the lack of return values, not all errors are reported via logcat under the DWAPI tag.  For example (and I’m unsure how prevalent this is) if you try to configure a profile to be associated with an application which is already associated with a different profile it fails with the following logcat output:


E/APIConfigurationManager: ApplicationAssociation: Package and Activity already associated to a different profile


So perhaps the -DWAPI tag will not give you the full picture if you see something not behaving as you would expect during development.


The ActiveProfile does not update immediately on application launch

The way the DataWedge service works is to continually monitor the foreground application and when it detects a change to the foreground app, it activates the appropriate profile.  For the API-exerciser application, when we switch to the app, DataWedge will detect the switch and activate the ‘DW API Exerciser Profile’ (if that is what you have called it).  Unfortunately it takes some finite amount of time for DataWedge to detect that the foreground application has changed and consequently if you invoke the GetActiveProfile API during onResume() you will probably be given stale information i.e. the profile which was in effect before the application was resumed.  The speed with which DataWedge detects the change has been improved starting in Android Marshmallow but you may still see stale information if you immediately retrieve the active profile in onResume().


I workaround this in the API exerciser application by retrieving the active profile some point after the application comes to the foreground, this workaround would be unacceptable in a production app since it relies on an arbitrary timeout – your application logic would need to be designed around retrieving the active profile just prior to it being required or re-checking if the active profile is unexpectedly reported as the launcher or default profile.


SetConfig can only configure one plugin at a time

The SetConfig API is incredibly useful, enabling you to modify any existing profile at runtime and allowing a slew of additional use cases.  For example, you can modify the active profile to only enable certain decoders at some points during your application workflow to prevent mis-scans. Before this API existed, you could have only achieved the same effect by having two separate profiles and switching between the two.


The nested structure of the configuration takes a bit to get your head around, unsurprising given that there are literally hundreds of parameters associated with each profile.


One thing to be aware of though is it is only possible to configure one plugin at a time, so if you want to configure two plugins it will require two calls to SetConfig.  I encountered this scenario in the API-Exerciser where I wanted to configure both the barcode input plugin and the intent output plugin – not difficult to do but given the complex structure of the profile bundle it was not entirely obvious at first how to do it.


SetConfig for the Barcode input plugin requires you to specify which scanner you are targeting

This is alluded to in the documentation for SetConfig but I did not appreciate the significance until I tried to change some barcode decoder properties for myself.  If you are using the DataWedge application UI and want to configure the barcode input plugin you go into the profile, select the options you want to change (e.g. EAN8 disabled) and then you are done, you have configured the settings for the selected scanner (by default, ‘auto’).


With the SetConfig API you need to specify the Intent Extra “current-device-id” to explicitly state which scanner you are configuring the settings for and since Zebra devices will usually have at least 3 scanners available (Camera, Imager and Bluetooth) and the default scanner being device specific you will need to ensure this is set correctly.  The current-device-id is obtained from a call to EnumerateScanners (6.3) so your application logic would first have to call EnumerateScanners and either prompt the user or search for a substring (e.g. Imager) in the returned scanner names. You cannot specify ‘auto’ or leave the setting blank to just apply the settings to the selected scanner, you must specify the ID but if you do leave it blank then it seems to default to 0.


What is the point of CreateProfile?

The SetConfig API has a CONFIG_MODE parameter which can be set to ‘Create_If_Not_Exist’, this will create the specified profile if it does not already exist (hence the name).  The only reason to create a profile via the CreateProfile API is if you want to create a profile without configuring any of its settings, this might be useful if you want to separate the logic between creating profiles and configuring those profiles but it is worth being aware of the potential duplication here.


Switching back to the current application’s associated profile will fail

Consider the following sequence of events:

  • Profile exists on device named ‘DW API Exerciser Profile’
  • ‘DW API Exerciser Profile’ is associated with the application ‘DataWedge API Exerciser’
  • That application is in the foreground so the ‘DW API Exerciser Profile’ is in effect.
  • Call SwitchToProfile to switch to ‘Profile0 (default)’.
    • This succeeds and ‘Profile0 (default)’ is now in effect
  • Call SwitchToProfile to switch back to ‘DW API Exerciser Profile’
    • This fails.
    • Error message in logcat is: “swithToProfile failed, ‘DW API Exerciser Profile’ is associated.”

I would classify this as a bug – the application the profile is associated with is the application trying to switch to it(!).  For the current release, it is required to work around this either by closing / relaunching the application or by using SetConfig to remove the application association from the profile.


Changes operate globally

DataWedge is a global service and any application on the device can interact with it to configure any profile.  It is obviously very flexible that any application is able to configure DataWedge however it goes without saying that care would need to be taken if multiple applications are trying to modify the same set of profiles.


There are no immediate plans to lock down the profile model or add some authentication mechanism on top of it since feedback from customers has been that the current architecture is a good trade-off between functionality and integration risk.


Moving forward as we give applications more power with what they can do using the DataWedge API, integration may become riskier.  For example, any application invoking the RestoreConfig API which completely resets DataWedge will need to play nicely with other applications on the device who may suddenly see their profiles being unexpectedly removed.


Next Steps

Zebra continues to improve and evolve its DataWedge APIs as these APIs are a response to customer requests who like the ease of use of DataWedge but would like to have more control over what applications can do with it at runtime.  I would expect many of the conditions listed above to disappear over time.


We are always interested in receiving feedback about the DataWedge APIs and what else we should expose moving forward.  To give feedback either speak with your Zebra representative or leave a comment on this blog post and I will ensure the DataWedge engineering team get to see it.

Submitted by Anonymous (not verified) on November 22, 2011 Permalink

Great article by all means, always recommend it to my customers and partners, thank you Darryn!

One question: as DataWedge provides a convenient GUI-based way to configure the scanner and EMDK brings a good way to dynamically control the scanner parameters - did anyone think of combining these so that EMDK could import a profile created in DataWedge?

I understand that it's possible by using GetConfig in DataWedge and then mapping the resulting structure to EMDK calls - but do we have more or less ready to use piece of code that implements this mapping in EMDK and can be offered to developers as a starting point?


Submitted by Anonymous (not verified) on November 28, 2011 Permalink

Hi David, I believe your question relates to Windows Mobile / CE from the context.  This article applies to Android but I will try to help.  I don't have a device to test but looking through the DataWedge Configuration guide, it says you can disable Profile0 but it is part of the advanced configuration mode: Profiles | Profile 0 | Enabled.  You cannot disable Profile0 with the basic configuration mode.  Whether disabling Profile0 will resolve your issue of co-existance with Enterprise Browser I cannot say but I suspect it will.

Submitted by David Garratt on November 28, 2011 Permalink

I would like a little clarification as to how DataWedge co-exists with Enterprise Browser. At the moment I have a web application which uses the javascript library that is shipped with EB to get barcode data directly from the scanner without data wedge. To me this seems like the best solution and I am assuming that DataWedge was originally at least intended to provide barcode data scanning capability to any application without coding. The problem I have found now is that if a Windows based MC92No has DataWedge running then the EB application is no longer able to capture events from the scan engine directly and all data is swallowed by DataWedge and stuffed into the keyboard buffer. I have looked for a way to disable the default profile so DataWedge does nothing unless it recognises a nominated application being run - but I've not been able to to it. Effectively its necessary to manually stop DW if EB is being run, but start DW if Telnet is being used. It's a bit clumsy and if the scanner is running in Kiosk mode the user can't actually do this themselves.

Have I missed some config ?

Submitted by Anonymous (not verified) on December 09, 2011 Permalink

Hi Andrew, the difficulty would be setting up the browser to listen for the broadcast intent.  If you are using Enterprise Browser you could follow the instructions in Abhineet's post: <a href=""></a> .  Another approach which might work is to use a websocket proxy which I have an unofficial guide for here: <a href="… to WebSockets Bridge – DARRYN CAMPBELL</a> .  Caveat: I do not have any experience with MS Dynamics 365.

Edit: Reading through your other question, it sounds like the Enterprise Browser approach would be most appropriate for yourself.

Submitted by Z-darryncampbe… on December 13, 2011 Permalink

Hi Alfred,

That lightening bolt is controlling the illumination mode of the scanner (turns the torch on or off when scanning) so I think that is a red herring.  The demo application is controlled by the DWDemo profile which is visible in the DataWedge UI.

There is a hidden Camera profile which disables DataWedge when the camera application is in the foreground (<a href="">DataWedge Profiles - Zebra Technologies TechDocs</a> ) so this should 'just work'.  It might be worth verifying there are no other profiles in DataWedge which are taking effect when the Camera is in the foreground.

I am afraid I don't have an MC3300 to verify I'm afraid but another thing to try is to add the camera to the Disabled app list (DataWedtge --&gt; Settings --&gt; Disabled app list)

Submitted by Anonymous (not verified) on December 13, 2011 Permalink

Glad it worked   The 'S' tells Soti that the extra being specified is a String.  It is a Soti thing and not an Android or Zebra thing.

Submitted by tu.pham@team.t… on December 14, 2011 Permalink

Hi<a href=""></a>​ ,

I've been able to restore to default and also delete a datawedge using sendintent but I am not having much luck with an IMPORT_CONFIG intent.

2019-02-20 08:32:12.891|pool-1-thread-1|D|AP|[LegacyScriptExecutor][execute]script command: sendintent [-b, intent:#Intent;action=com.symbol.datawedge.api.ACTION;;end], result: ScriptResult{description='OK', resultType=OK}|

It looks ok in the logs but Datawedge is crashing on the device everytime i send the intent. Any tips on how I can fix this?

Submitted by Anonymous (not verified) on December 14, 2011 Permalink

I only have experience with delivering the entire datawedge.db file and then using AutoImport - that works well and I actually wrote a developer article on that: <a href=""></a>  but the intent mechanism would also work.  If it were me starting a new app I would opt for the sendIntent method since it is more flexible / controllable.

Submitted by Anonymous (not verified) on December 14, 2011 Permalink

Hi Tu, you are passing a string to IMPORT_CONFIG but according to <a href="">Import Config - Zebra Technologies TechDocs</a> , this API requires a bundle.  It does not look like it is possible to send intents with bundles via SOTI according to <a href="… sendintent Command</a> so you may need to import the profile by some other means, for example as I described in the 'mass deploy datawedge profiles...' link I previously shared.

Submitted by Alfred Teixeira on December 16, 2011 Permalink

Hello Darryn,

Is there a way through DataWedge or the EMDK to disable the scanner when the camera is in use? A customer with the MC3300 needs to take pictures of barcodes to be consumed by his application. Due to the scanner being on when the camera is on, the pictures of the barcodes are not readable. The customer commented that the DataWedge Demo app includes a command button with a lightning bolt on it; this button appears to provide the required functionality; however, I cannot seem to find the information necessary to achieve this result.

Submitted by Alfred Teixeira on December 16, 2011 Permalink

Hello Darryn,

I appreciate the quick response. With respect to the profile you suggest, would that provide the equivalent functionality as toggling the lightning bolt button via the DataWedge Demo app? I ask because this is what the customer seeks to do: programmatically control that feature.

The customer cannot add the Camera to the disabled application list because it is the camera's functionality that the customer wishes to make use of, not the scanner. The customer requires a picture of the barcode to be taken; he is not interested in using a scanner to read the barcode data.

The customer wants to disable the scanner to make use of the camera.

Thank you again for your prompt response.


Submitted by Anonymous (not verified) on December 16, 2011 Permalink


I think we are talking at cross purposes, I am saying that the lightening bolt is unrelated to the camera and may be a red herring.  You CAN simulate the effect of the lightening bolt by modifying the scanner illumination mode but that would not have any impact on the camera.  There is an example of changing illumination mode in my Kotlin application: <a href="… at master · darryncampbell/DataWedgeKotlin · GitHub</a>

The Disabled app list is the list of applications for which DataWedge is disabled, not a list of applications which are themselves disabled.

Submitted by Anonymous (not verified) on May 04, 2019 Permalink

Thanks Valery,

So this would be a way to configure the <strong>scanner defaults</strong> for an application using the EMDK API?  I think it might get complicated to mix it with DataWedge but you're just saying DataWedge as an example of a GUI based way to set scanner defaults, if I understand correctly.

Are there any customers asking for this specifically?  I remember we had this capability on Windows Mobile / CE but I can't for the life of me remember the tool.


Submitted by Anonymous (not verified) on May 04, 2019 Permalink

It's a request from one of our ISVs - not the requirement but just "would be good to have this feature". Customers may want to configure some features for a specific use case - like trigger/aim  type or feedback settings or disable/enable some specific decoders etc. It does not make much sense to create its own GUI within any scanning app so having a "system-wide" GUI-based utility to configure some common defaults can be quite useful.

Indeed on WM and CE we had our CtlPanel app that was able to configure many scanner parameters (but not decoders) and save them in registry on the driver level so these settings become the default ones for any app using the scanner.

Submitted by 7de8b982-009d-… on May 04, 2019 Permalink

I have raised a question in these forums about getting Datawedge scan data into MS Dynamics 365 when there is no cursor on an input field.  Can Android intents be used to "broadcast" the scan data to the Dynamics 365 browser app to get around the need for an input field?  if so how would I set that up?

Submitted by Alfred Teixeira on May 04, 2019 Permalink

Hello Darryn,

I appreciate the clarification.

The reason for my previous follow up is that the customer mentioned that the he encounters with his own application is mitigated in the Datawedge Demo through toggling the lightning bolt button.

I will download and review the application you referenced in your response.

Thank you for your assistance.


Submitted by Anonymous (not verified) on May 04, 2019 Permalink

Thanks Tu, I don't have a SOTI instance to test but I note they do support the ability to send intents: <a href="… sendintent Command</a> .  To do this via adb you can run:
&gt; adb shell am broadcast -a com.symbol.datawedge.api.ACTION -e com.symbol.datawedge.api.RESTORE_CONFIG ''
So in SOTI I imagine the command will be something like:
sendintent -b "intent:#Intent;action=com.symbol.datawedge.api.ACTION;'';end"

Submitted by tu.pham@team.t… on May 04, 2019 Permalink

Love your work Darren. Is there a way to trigger these intents via an MDM script like SOTI MobiControl?

I have a requirement to push a payload that resets the Datawedge config on a TC56 device running Nougat . Does anybody here know if it's achievable?

Submitted by tu.pham@team.t… on May 04, 2019 Permalink

OMG it worked 100% direct copy and paste.

I was missing the “S.” in front of the “"

What does adding the ’S’ actually do?

Submitted by tu.pham@team.t… on May 04, 2019 Permalink

Could I pick your brains on how you would (in your wisdom) tackle this solution?

This is the current set of steps to configure datawedge:

1. Restore Datawedge to factory defaults
2. Disable all default profiles (Profile0, Launcher, DWDemo)
3. Import up to 4 x Datawedge profiles

Ive currently got steps 1-2 configured as part of a baseline build then each individual datawedge is packaged with its associated app using the “AutoImport” folder.

Would you export the entire configuration into a single datawedge.db file?  Then would you deliver the datawedge.db file using a sendintent command or the AutoImport method?

Appreciate the help!