You can also add other functions from 3rd party Python packages or ones you write yourself as pivot functions.
Terminology
Before we get into things let's clear up a few terms.
Entities - These are Python classes that represent real-world objects commonly encountered in CyberSec investigations and hunting. E.g., Host, URL, IP Address, Account, etc.
Pivoting - This comes from the common practice in CyberSec investigations of navigating from one suspect entity to another. E.g., you might start with an alert identifying a potentially malicious IP Address, from there you 'pivot' to see which hosts or accounts were communicating with that address. From there you might pivot again to look at processes running on the host or Office activity for the account.
Background reading
This article is available in Notebook form so that you can try out the examples. [TODO]
There is also full documentation of the Pivot functionality on our ReadtheDocs page.
Life before pivot functions
Before Pivot functions your ability to use the various bits of functionality in MSTICPy was always bounded by your knowledge of where a certain function was (or your enthusiasm for reading the docs).
For example, suppose you had an IP address that you wanted to do some simple enrichment on.
ip_addr="20.72.193.242"
First, you'd need to locate and import the functions. There might also be (as in the GeoIPLiteLookup class) some initialization step you'd need to do before using the functionality.
Next you might have to check the help for each function to work it parameters.
>>> help(get_ip_type) Help on function get_ip_type in module msticpy.sectools.ip_utils: get_ip_type(ip: str = None, ip_str: str = None) -> str Validate value is an IP address and deteremine IPType category. ...
At which point you'd discover that the output from each function was somewhat raw and it would take a bit more work if you wanted to combine it in any way (say in a single table).
In the rest of the article we'll show you how Pivot functions make it easier to discover data and enrichment functions. We'll also show how pivot functions bring standardization and handle different types of input (including lists and DataFrames) and finally, how the standardized output lets you chain multiple pivot functions together into re-usable pipelines of functionality.
Getting started with pivot functions
Let's get started with how to use Pivot functions.
Typically, we use MSTICPy's init_notebook function at the start of any notebook. This handles checking versions and importing some commonly-used packages and modules (both MSTICPy and 3rd party packages like pandas).
Then there are a couple of preliminary steps needed before you can use pivot functions. The main one is loading the Pivot class.
Pivot functions are added to the entities dynamically by the Pivot class. The Pivot class will try to discover relevant functions from queries, Threat Intel providers and various utility functions.
In some cases, notably data queries, the data query functions are themselves created dynamically, so these need to be loaded before you create the Pivot class. (You can always create a new instance of this class, which forces re-discovery, so don't worry if mess up the order of things).
Note in most cases we don't need to connect/authenticate to a data provider prior to loading Pivot.
Let's load our data query provider for AzureSentinel.
Pivot searches through the current objects defined in the Python/notebook namespace to find provider objects that it will use to create the pivot functions. This is most relevant for QueryProviders - when you create a Pivot class instance it will find and use the relevant queries from the az_provider object that we created in the previous step. In most other cases (like GeoIP and ThreatIntel providers, it will create new ones if it can't find existing ones).
Easy discovery of functionality
Find the entity name you need
The simplest way to do this is simply enumerate (using Python dir() function) the contents of the MSTICPy entities sub-package. This should have already been imported by the init_notebook function that we ran earlier.
The items at the beginning of the list with proper capitalization are the entities.
We're going to make this a little more elegant in a forthcoming update with this helper function.
>>> entities.find_entity("ip") Match found 'IpAddress'msticpy.datamodel.entities.ip_address.IpAddress
Listing pivot functions available for an entity
Note you can always address an entity using its qualified path, e.g. "entities.IpAddress" but if you are going to use one or two entities a lot, it will save a bit of typing if you import them explicitly.
Once you have the entity loaded, you can use the get_pivot_list() function to see which pivot functions are available for it. The example below has been abbreviated for space reasons.
Some of the function names are a little unwieldy but, in many cases, this is necessary to avoid name collisions. You will notice from the list that the functions are grouped into containers: "AzureSentinel", "ti" and "util" in the above example.
Although this makes the function name even longer, we thought that this helped to keep related functionality together - so you don't get a TI lookup function, when you thought you were running a query.
Fortunately, Jupyter notebooks/IPython support tab completion so you should not normally have to remember these names.
The containers ("AzureSentinel", "util", etc.) are also callable functions - they just return the list of functions they contain.
>>> IpAddress.util() whois functionip_type functionip_rev_resolve functiongeoloc_mm functiongeoloc_ips function
Now we're ready to run any of the functions for this entity (we take the same initial examples from the "Life before pivot functions" plus a few more).
>>> IpAddress.util.ip_type(ip_addr)
ip
result
0
20.72.193.242
Public
>>> IpAddress.util.whois(ip_addr)
asn
asn_cidr
asn_country_code
asn_date
asn_description
asn_registry
nets
0
8075
20.64.0.0/10
US
2017-10-18
MICROSOFT-CORP-MSN-AS-BLOCK, US
arin
[{'cidr': '20.128.0.0/16, 20.48, ...
>>> IpAddress.util.ip_rev_resolve(ip_addr)
qname
rdtype
response
ip_address
0
20.72.193.242
PTR
The DNS query name does not exist: 20.72.193.242.
20.72.193.242
>>> IpAddress.util.geoloc_mm(ip_addr)
CountryCode
CountryName
State
City
Longitude
Latitude
Asn
edges
Type
AdditionalData
IpAddress
0
US
United States
Washington
None
-122.3412
47.6032
None
{}
geolocation
{}
20.72.193.242
>>> IpAddress.ti.lookup_ip(ip_addr)
Ioc
IocType
SafeIoc
QuerySubtype
Provider
Result
Severity
Details
0
20.72.193.242
ipv4
20.72.193.242
None
Tor
True
information
Not found.
0
20.72.193.242
ipv4
20.72.193.242
None
VirusTotal
True
unknown
{'verbose_msg': 'Missing IP address', 'response_code': 0}
Notice that we didn't need to worry about either the parameter name or format (more on this in the next section). Also, whatever the function, the output is always returned as a pandas DataFrame.
For Data query functions you do need to worry about the parameter name
Data query functions are slightly more complex than most other functions and specifically often support many parameters. Rather than try to guess which parameter you meant, we require you to be explicit about it.
Before we can use a data query, we need to authenticate to the provider.
If you are not sure of the parameters required by the query you can use the built-in help
>>> Host.AzureSentinel.SecurityAlert_list_related_alerts? Signature: Host.AzureSentinel.SecurityAlert_list_related_alerts(*args,**kwargs)-> Union[pandas.core.frame.DataFrame, Any] Docstring: Retrieves list of alerts with a common host, account or process Parameters ---------- account_name: str (optional) The account name to find add_query_items: str (optional) Additional query clauses end: datetime (optional) Query end time host_name: str (optional) The hostname to find path_separator: str (optional) Path separator (default value is: \\) process_name: str (optional) ...
Shown below is a preview of a notebook tool that lets you browser around entities and their pivot functions, search for a function by keyword and view the help for that function. This is going to be released shortly.
>>> Pivot.browse()
Standardized way of calling Pivot functions
Due to various factors (historical, underlying data, developer laziness and forgetfulness, etc.) the functionality in MSTICPy can be inconsistent in the way it uses input parameters.
Also, many functions will only accept inputs as a single value, or a list or a DataFrame or some unpredictable combination of these.
Pivot functions allow you to largely forget about this - you can use the same function whether you have:
a single value
a list of values (or any Python iterable, such as a tuple or even a generator function)
a DataFrame with the input value in one of the columns.
Let's take an example.
Suppose we have a set of IP addresses pasted from somewhere that we want to use as input.
We need to convert this into a Python data object of some sort.
To do this we can use another Pivot utility %%txt2df. This is a Jupyter/IPython magic function - to use it, just paste you data in a cell that you want to import into an empty. Use
%%txt2df --help
in an empty cell to see the full syntax.
In the example below, we specify a comma separator, that the data has a headers row and to save the converted data as a DataFrame named "ip_df".
Warning if you specify the "--name" parameter, this will overwrite any existing variable of this name.
%%txt2df --sep , --headers --name ip_df idx, ip, type 0, 172.217.15.99, Public 1, 40.85.232.64, Public 2, 20.38.98.100, Public 3, 23.96.64.84, Public 4, 65.55.44.108, Public 5, 131.107.147.209, Public 6, 10.0.3.4, Private 7, 10.0.3.5, Private 8, 13.82.152.48, Public
idx
ip
type
0
0
172.217.15.99
Public
1
1
40.85.232.64
Public
2
2
20.38.98.100
Public
3
3
23.96.64.84
Public
4
4
65.55.44.108
Public
5
5
131.107.147.209
Public
6
6
10.0.3.4
Private
7
7
10.0.3.5
Private
8
8
13.82.152.48
Public
For demonstration purposes, we'll also create a standard Python list from the "ip" column of the DataFrame.
If you recall the earlier example of get_ip_type, passing it a list or DataFrame doesn't result in anything useful.
>>> get_ip_type(ip_list) ['172.217.15.99', '40.85.232.64', '20.38.98.100', '23.96.64.84', '65.55.44.108', '131.107.147.209', '10.0.3.4', '10.0.3.5', '13.82.152.48'] does not appear to be an IPv4 or IPv6 address 'Unspecified'
Pivot versions are (somewhat) agnostic to input data format
However, the "pivotized" version can accept and correctly process a list.
>>> IpAddress.util.ip_type(ip_list)
ip
result
0
172.217.15.99
Public
1
40.85.232.64
Public
2
20.38.98.100
Public
3
23.96.64.84
Public
4
65.55.44.108
Public
5
131.107.147.209
Public
6
10.0.3.4
Private
7
10.0.3.5
Private
8
13.82.152.48
Public
In the case of a DataFrame, we have to tell the function the name of the column that contains the input data.
>>> IpAddress.util.whois(ip_df) # won't work! --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-32-debf57d805c7> in <module> ... 173 input_df, input_column, param_dict = _create_input_df( --> 174 input_value, pivot_reg, parent_kwargs=kwargs 175 ) ... KeyError: ("'ip_column' is not in the input dataframe", 'Please specify the column when calling the function. You can use one of the parameter names for this:', ['column', 'input_column', 'input_col', 'src_column', 'src_col'])
Note: for most functions you can ignore the parameter name and just specify it as a positional parameter. You can also use the original parameter name of the underlying function or the placeholder name "value".
You can also pass an entity instance of an entity as a input parameter. The pivot code knows which attribute or attributes of an entity will provider the input value.
Iterable/DataFrame inputs and single-value functions
Many of the underlying functions only accept single values as inputs. Examples of these are the data query functions - typically they expect a single host name, IP address, etc.
Pivot knows about the type of parameters that the function accepts. It will adjust the input to match the expectations of the underlying function. If a list or DataFrame is passed as input to a single-value function Pivot will split the input and call the function once for each value. It then combines the output into a single DataFrame before returning the results.
You can read a bit more about how this is done in the Appendix - "how do pivot wrappers work?"
Data queries - where does the time range come from?
The Pivot class has a built-in time range, which is used by default for all queries. Don't worry - you can change it easily.
Note: "Pivot.current" gives you access to the last created instance of the Pivot class - if you've created multiple instances of Pivot (which you rarely need to do), you can always get to the last one you created using this class attribute.
You can edit the time range interactively
Pivot.current.edit_query_time()
Or by setting the timespan property directly.
>>> from msticpy.common.timespan import TimeSpan >>> # TimeSpan accepts datetimes or datestrings >>> timespan = TimeSpan(start="02/01/2021", end="02/15/2021") >>> Pivot.current.timespan = timespan TimeStamp(start=2021-02-01 00:00:00, end=2021-02-15 00:00:00, period=-14 days +00:00:00)
In an upcoming release there is also a convenience function for setting the time directly with Python datetimes or date strings.
The Pivot layer will pass any unused keyword parameters to the underlying function. This does not usually apply to positional parameters - if you want parameters to get to the function, you have to name them explicitly. In this example the add_query_items parameter is passed to the underlying query function
>>> entities.Host.AzureSentinel.SecurityEvent_list_host_logons( host_name="victimPc", add_query_items="| summarize count() by LogonType" )
LogonType
count_
0
5
27492
1
4
12597
2
3
6936
3
2
173
4
10
58
5
9
8
6
0
19
7
11
1
Pivot Pipelines
Because all pivot functions accept DataFrames as input and produce DataFrames as output, it means that it is possible to chain pivot functions into a pipeline.
Joining input to output
You can join the input to the output. This usually only makes sense when the input is a DataFrame. It lets you keep the previously accumulated results and tag on the additional columns produced by the pivot function you are calling.
The join parameter supports "inner", "left", "right" and "outer" joins (be careful with the latter though!) See pivot joins documentation for more details.
Although joining is useful in pipelines you can use it on any function whether in a pipeline or not. In this example you can see that the idx, ip and type columns have been carried over from the source DataFrame and joined with the output.
Here is an example of using it to call four pivot functions, each using the output of the previous function as input and using the join parameter to accumulate the results from each stage.
The whole thing is surrounded by a pair of parentheses - this is just to let us split the whole expression over multiple lines without Python complaining.
Next we have ips_df - this is just the starting DataFrame, our input data.
Next we call the mp_pivot.run() accessor method on this dataframe. We pass it the pivot function that we want to run (IpAddress.util.ip_type) and the input column name (IP). This column name is the column in ips_df where our input IP addresses are. We've also specified an join type of "inner". In this case the join type doesn't really matter since we know we get exactly one output row for every input row.
We're using the pandas query function to filter out unwanted entries from the previous stage. In this case we only want "Public" IP addresses. This illustrates that you can intersperse standard pandas functions in the same pipeline. We could have also added a column selector expression ([["col1", "col2"...]]), for example, if we wanted to filter the columns passed to the next stage
We are calling a further pivot function - whois. Remember the "column" parameter always refers to the input column, i.e. the column from previous stage that we want to use in this stage.
We are calling geoloc_mm to get geo location details joining with a "left" join - this preserves the input data rows and adds null columns in any cases where the pivot function returned no result.
Is the same as 6 except the called function is a data query to see if we have any alerts that contain these IP addresses. Remember, in the case of data queries we have to name the specific query parameter that we want the input to go to. In this case, each row value in the ip column from the previous stage will be sent to the query.
Finally we close the parentheses to form a valid Python expression. The whole expression returns a DataFrame so we can add further pandas operations here (like .head(5) shown here).
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
Threat Intelligence Alerts
Microsoft
91d806d3-6b6f-4e5c-a78f-e674d602be51
625ff9af-dddc-0cf8-9d4b-e79067fa2e71
ThreatIntelligence
83
1
8ecf8077-cf51-4820-aadd-14040956f35d
2020-12-23 14:08:12+00:00
Microsoft Threat Intelligence Analytics
Microsoft Threat Intelligence Analytics
Medium
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
Threat Intelligence Alerts
Microsoft
173063c4-10dd-4dd2-9e4f-ec5ed596ec54
c977f904-ab30-d57e-986f-9d6ebf72771b
ThreatIntelligence
83
2
8ecf8077-cf51-4820-aadd-14040956f35d
2020-12-23 14:08:12+00:00
Microsoft Threat Intelligence Analytics
Microsoft Threat Intelligence Analytics
Medium
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
Threat Intelligence Alerts
Microsoft
58b2cda2-11c6-42b8-b6f1-72751cad8f38
9ee547e4-cba1-47d1-e1f9-87247b693a52
ThreatIntelligence
83
Other pipeline functions
In addition to run, the mp_pivot accessor also has the following functions:
display - this simply displays the data at the point called in the pipeline. You can add an optional title, filtering and the number or rows to display
tee - this forks a copy of the DataFrame at the point it is called in the pipeline. It will assign the forked copy to the name given in the var_name parameter. If there is an existing variable of the same name it will not overwrite it unless you add the clobber=True parameter.
In both cases the pipelined data is passed through unchanged.
Use of these is shown below in this partial pipeline.
... .mp_pivot.run(IpAddress.util.geoloc_mm, column="ip", join="left") .mp_pivot.display(title="Geo Lookup", cols=["IP", "City"]) # << display an intermediate result .mp_pivot.tee(var_name="geoip_df", clobber=True) # << save a copy called 'geoip_df' .mp_pivot.run(IpAddress.AzureSentinel.SecurityAlert_list_alerts_for_ip, source_ip_list="ip", join="left")
In the next release we've also implemented:
tee_exec - this executes a function on a forked copy of the DataFrame The function must be a pandas function or custom accessor. A good example of the use of this might be creating a plot or summary table to display partway through the pipeline.
Extending Pivot - adding your own (or someone else's) functions
You can add pivot functions of your own. You need to supply:
the function
some metadata that describes where the function can be found and how the function works
The current version of Pivot doesn't let you add functions defined inline (i.e. written in the notebook itself) but this will be possible in the forthcoming release.
Let's create a function in a Python module my_module.py. We can do this using the %%write_file magic function and running the cell.
%%writefile my_module.py """Upper-case and hash""" from hashlib import md5
def my_func(input: str): md5_hash = "-".join(hex(b)[2:] for b in md5(input.encode("utf-8")).digest()) return { "Title": input.upper(), "Hash": md5_hash }
We also need to create a YAML definition file for our pivot function. Again we can use %%write_file to create a local file in the current directory. We need to tell Pivot
the name of the function and source module,
the name of the container that the function will appear in,
the input type expected by the function ("value", "list" or "dataframe")
which entities to add the pivot to, along with a corresponding attribute of the entity. (The attribute is used in cases where you are passing an instance of an entity itself as an input parameter - if in doubt just use any valid attribute of the entity).
The name of the input attribute of the underlying function.
Now we can register the function we created as a pivot function.
>>> from msticpy.datamodel.pivot_register_reader import register_pivots >>> register_pivots("my_func.yml")
An then run it.
>>> Host.cyber.upper_hash_name("host_name")
Title
Hash
input
0
HOST_NAME
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
host_name
In the next release, this will be available as a simple function that can be used to add a function defined in the notebook as shown here.
from hashlib import md5
def my_func2(input: str): md5_hash = "-".join(hex(b)[2:] for b in md5(input.encode("utf-8")).digest()) return { "Title": input.upper(), "Hash": md5_hash }
Pivot.add_pivot_function( func=my_func2, container="cyber", # which container it will appear in on the entity input_type="value", entity_map={"Host": "HostName"}, func_input_value_arg="input", func_new_name="il_upper_hash_name", )
Host.cyber.il_upper_hash_name("host_name")
Title
Hash
input
0
HOST_NAME
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
host_name
Conclusion
We've taken a short tour through the MSTICPy Pivot functions, looking at how they make the functionality in MSTICPy easier to discover and use.
I'm particularly excited about the pipeline functionality. In the next release we're going to make it possible to define reusable pipelines in configuration files and execute them with a single function call. This should help streamline some common patterns in notebooks for Cyber hunting and investigation.
In Python you can create functions that return other functions. This is called wrapping the function.
It allows the outer function to do additional things to the input parameters and the return value of the inner function.
Take this simple function that just applies proper capitalization to an input string.
def print_me(arg): print(arg.capitalize())
print_me("hello")
Hello
If we try to pass a list to this function we get an expected exception since the function only knows how to process a string
print_me(["hello", "world"]) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-36-94b3e61eb86f> in <module> … AttributeError: 'list' object has no attribute 'capitalize'
We could create a wrapper function that checked the input and iterated over the individual items if arg is a list. The works but we don't want to have to do this for every function that we want to have flexible input!
def print_me_list(arg): if isinstance(arg, list): for item in arg: print_me(item) else: print_me(arg)
In the example below, the outer function dont_care_func defines an inner function - list_or_str - and then returns this function. The inner function list_or_str is what implements the same "is-this-a-string-or-list" logic that we saw in the previous example. Crucially though, it isn't hard-coded to call print_me but calls whatever function is passed (the func parameter) to it from the outer function dont_care_func.
# Our magic wrapper def dont_care_func(func):
def list_or_str(arg): if isinstance(arg, list): for item in arg: func(item) else: func(arg) return list_or_str
How do we use this?
We simply pass the function that we want to wrap to dont_care_func. Recall, that this function just returns an instance of the inner function. In this case the value func will have been replaced by the actual function print_me.
print_stuff = dont_care_func(print_me)
Now we have a wrapped version of print_me that can handle different types of input. Magic!
The wrapper functionality in Pivot is a bit more complex than this but essentially operates this way.
Updated Mar 23, 2021
Version 2.0
No CommentsBe the first to comment
"}},"componentScriptGroups({\"componentId\":\"custom.widget.MicrosoftFooter\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/QueryHandler\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCoverImage\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCoverImage-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeTitle\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTimeToRead\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserRank\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTime\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTime-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCustomFields\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRevision\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRevision-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyButton\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageAuthorBio\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeDescription\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744658874071"}],"cachedText({\"lastModified\":\"1744658874071\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeIcon\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1744658874071"}]},"CachedAsset:pages-1745160782375":{"__typename":"CachedAsset","id":"pages-1745160782375","value":[{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"UserBlogPermissions.Page","type":"COMMUNITY","urlPath":"/c/user-blog-permissions/page","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730142000000,"localOverride":null,"page":{"id":"AllEvents","type":"CUSTOM","urlPath":"/Events","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730142000000,"localOverride":null,"page":{"id":"CommunityHub.Page","type":"CUSTOM","urlPath":"/Directory","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730142000000,"localOverride":null,"page":{"id":"AllBlogs.Page","type":"CUSTOM","urlPath":"/blogs","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1745160782375,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Deleted","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"MMM dd yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":"en","possibleValues":["en-US"]}},"deleted":false},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"Category:category:microsoft-sentinel":{"__typename":"Category","id":"category:microsoft-sentinel","entityType":"CATEGORY","displayId":"microsoft-sentinel","nodeType":"category","depth":4,"title":"Microsoft Sentinel","shortTitle":"Microsoft Sentinel","parent":{"__ref":"Category:category:microsoft-security"}},"Category:category:top":{"__typename":"Category","id":"category:top","displayId":"top","nodeType":"category","depth":0,"title":"Top","entityType":"CATEGORY","shortTitle":"Top"},"Category:category:communities":{"__typename":"Category","id":"category:communities","displayId":"communities","nodeType":"category","depth":1,"parent":{"__ref":"Category:category:top"},"title":"Communities","entityType":"CATEGORY","shortTitle":"Communities"},"Category:category:products-services":{"__typename":"Category","id":"category:products-services","displayId":"products-services","nodeType":"category","depth":2,"parent":{"__ref":"Category:category:communities"},"title":"Products","entityType":"CATEGORY","shortTitle":"Products"},"Category:category:microsoft-security":{"__typename":"Category","id":"category:microsoft-security","displayId":"microsoft-security","nodeType":"category","depth":3,"parent":{"__ref":"Category:category:products-services"},"title":"Microsoft Security","entityType":"CATEGORY","shortTitle":"Microsoft Security","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Blog:board:MicrosoftSentinelBlog":{"__typename":"Blog","id":"board:MicrosoftSentinelBlog","entityType":"BLOG","displayId":"MicrosoftSentinelBlog","nodeType":"board","depth":5,"conversationStyle":"BLOG","title":"Microsoft Sentinel Blog","description":"
Microsoft Sentinel is a cloud-native SIEM, enriched with AI and automation to provide expansive visibility across your digital environment.
Pivot functions in MSTICPy and Azure Sentinel Notebooks
\n
Pivot functions attached to entities allow you to quickly find the query, threat intel or enrichment function you need. They support multiple input types and can be chained together to produce powerful pipelines.
\n
\n
","body":"
We recently released a new version of MSTICPy with a feature called Pivot functions.
\n
This feature has three main goals:
\n
\n
Making it easy to discover and invoke MSTICPy functionality.
\n
Creating a standardized way to call pivotable functions.
\n
Letting you assemble multiple functions into re-usable pipelines.
\n
\n
The pivot functionality exposes operations relevant to a particular entity as methods (or functions) of that entity. These operations include:
\n
\n
Data queries
\n
Threat intelligence lookups
\n
Other data lookups such as geo-location or domain resolution
\n
and other local functionality
\n
\n
Here are a couple of examples showing calling different kinds of enrichment functions from the IpAddress entity:
You can also add other functions from 3rd party Python packages or ones you write yourself as pivot functions.
\n
\n
Terminology
\n
Before we get into things let's clear up a few terms.
\n
\n
Entities - These are Python classes that represent real-world objects commonly encountered in CyberSec investigations and hunting. E.g., Host, URL, IP Address, Account, etc.
\n
\n
Pivoting - This comes from the common practice in CyberSec investigations of navigating from one suspect entity to another. E.g., you might start with an alert identifying a potentially malicious IP Address, from there you 'pivot' to see which hosts or accounts were communicating with that address. From there you might pivot again to look at processes running on the host or Office activity for the account.
\n
\n
Background reading
\n
This article is available in Notebook form so that you can try out the examples. [TODO]
\n
There is also full documentation of the Pivot functionality on our ReadtheDocs page.
\n
\n
Life before pivot functions
\n
\n
Before Pivot functions your ability to use the various bits of functionality in MSTICPy was always bounded by your knowledge of where a certain function was (or your enthusiasm for reading the docs).
\n
For example, suppose you had an IP address that you wanted to do some simple enrichment on.
\n
ip_addr=\"20.72.193.242\"
\n
First, you'd need to locate and import the functions. There might also be (as in the GeoIPLiteLookup class) some initialization step you'd need to do before using the functionality.
Next you might have to check the help for each function to work it parameters.
\n
>>> help(get_ip_type) Help on function get_ip_type in module msticpy.sectools.ip_utils: get_ip_type(ip: str = None, ip_str: str = None) -> str Validate value is an IP address and deteremine IPType category. ...
At which point you'd discover that the output from each function was somewhat raw and it would take a bit more work if you wanted to combine it in any way (say in a single table).
\n
\n
In the rest of the article we'll show you how Pivot functions make it easier to discover data and enrichment functions. We'll also show how pivot functions bring standardization and handle different types of input (including lists and DataFrames) and finally, how the standardized output lets you chain multiple pivot functions together into re-usable pipelines of functionality.
\n
\n
Getting started with pivot functions
\n
\n
Let's get started with how to use Pivot functions.
\n
Typically, we use MSTICPy's init_notebook function at the start of any notebook. This handles checking versions and importing some commonly-used packages and modules (both MSTICPy and 3rd party packages like pandas).
Then there are a couple of preliminary steps needed before you can use pivot functions. The main one is loading the Pivot class.
\n
\n
Pivot functions are added to the entities dynamically by the Pivot class. The Pivot class will try to discover relevant functions from queries, Threat Intel providers and various utility functions.
\n
\n
In some cases, notably data queries, the data query functions are themselves created dynamically, so these need to be loaded before you create the Pivot class. (You can always create a new instance of this class, which forces re-discovery, so don't worry if mess up the order of things).
\n
\n
Note in most cases we don't need to connect/authenticate to a data provider prior to loading Pivot.
\n
\n
Let's load our data query provider for AzureSentinel.
Pivot searches through the current objects defined in the Python/notebook namespace to find provider objects that it will use to create the pivot functions. This is most relevant for QueryProviders - when you create a Pivot class instance it will find and use the relevant queries from the az_provider object that we created in the previous step. In most other cases (like GeoIP and ThreatIntel providers, it will create new ones if it can't find existing ones).
\n
\n
Easy discovery of functionality
\n
\n
Find the entity name you need
\n
The simplest way to do this is simply enumerate (using Python dir() function) the contents of the MSTICPy entities sub-package. This should have already been imported by the init_notebook function that we ran earlier.
\n
The items at the beginning of the list with proper capitalization are the entities.
We're going to make this a little more elegant in a forthcoming update with this helper function.
\n
>>> entities.find_entity(\"ip\") Match found 'IpAddress'msticpy.datamodel.entities.ip_address.IpAddress
\n
Listing pivot functions available for an entity
\n
Note you can always address an entity using its qualified path, e.g. \"entities.IpAddress\" but if you are going to use one or two entities a lot, it will save a bit of typing if you import them explicitly.
Once you have the entity loaded, you can use the get_pivot_list() function to see which pivot functions are available for it. The example below has been abbreviated for space reasons.
Some of the function names are a little unwieldy but, in many cases, this is necessary to avoid name collisions. You will notice from the list that the functions are grouped into containers: \"AzureSentinel\", \"ti\" and \"util\" in the above example.
\n
\n
Although this makes the function name even longer, we thought that this helped to keep related functionality together - so you don't get a TI lookup function, when you thought you were running a query.
\n
Fortunately, Jupyter notebooks/IPython support tab completion so you should not normally have to remember these names.
\n
\n
\n
\n
The containers (\"AzureSentinel\", \"util\", etc.) are also callable functions - they just return the list of functions they contain.
\n
>>> IpAddress.util() whois functionip_type functionip_rev_resolve functiongeoloc_mm functiongeoloc_ips function
\n
Now we're ready to run any of the functions for this entity (we take the same initial examples from the \"Life before pivot functions\" plus a few more).
\n
\n
>>> IpAddress.util.ip_type(ip_addr)
\n
\n\n
\n
\n
\n
ip
\n
\n
\n
result
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
>>> IpAddress.util.whois(ip_addr)
\n
\n\n
\n
\n
\n
asn
\n
\n
\n
asn_cidr
\n
\n
\n
asn_country_code
\n
\n
\n
asn_date
\n
\n
\n
asn_description
\n
\n
\n
asn_registry
\n
\n
\n
nets
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
8075
\n
\n
\n
20.64.0.0/10
\n
\n
\n
US
\n
\n
\n
2017-10-18
\n
\n
\n
MICROSOFT-CORP-MSN-AS-BLOCK, US
\n
\n
\n
arin
\n
\n
\n
[{'cidr': '20.128.0.0/16, 20.48, ...
\n
\n
\n\n
\n
\n
>>> IpAddress.util.ip_rev_resolve(ip_addr)
\n
\n\n
\n
\n
\n
qname
\n
\n
\n
rdtype
\n
\n
\n
response
\n
\n
\n
ip_address
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
PTR
\n
\n
\n
The DNS query name does not exist: 20.72.193.242.
\n
\n
\n
20.72.193.242
\n
\n
\n\n
\n
\n
>>> IpAddress.util.geoloc_mm(ip_addr)
\n
\n\n
\n
\n
\n
CountryCode
\n
\n
\n
CountryName
\n
\n
\n
State
\n
\n
\n
City
\n
\n
\n
Longitude
\n
\n
\n
Latitude
\n
\n
\n
Asn
\n
\n
\n
edges
\n
\n
\n
Type
\n
\n
\n
AdditionalData
\n
\n
\n
IpAddress
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
US
\n
\n
\n
United States
\n
\n
\n
Washington
\n
\n
\n
None
\n
\n
\n
-122.3412
\n
\n
\n
47.6032
\n
\n
\n
None
\n
\n
\n
{}
\n
\n
\n
geolocation
\n
\n
\n
{}
\n
\n
\n
20.72.193.242
\n
\n
\n\n
\n
\n
>>> IpAddress.ti.lookup_ip(ip_addr)
\n
\n\n
\n
\n
\n
Ioc
\n
\n
\n
IocType
\n
\n
\n
SafeIoc
\n
\n
\n
QuerySubtype
\n
\n
\n
Provider
\n
\n
\n
Result
\n
\n
\n
Severity
\n
\n
\n
Details
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
ipv4
\n
\n
\n
20.72.193.242
\n
\n
\n
None
\n
\n
\n
Tor
\n
\n
\n
True
\n
\n
\n
information
\n
\n
\n
Not found.
\n
\n
\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
ipv4
\n
\n
\n
20.72.193.242
\n
\n
\n
None
\n
\n
\n
VirusTotal
\n
\n
\n
True
\n
\n
\n
unknown
\n
\n
\n
{'verbose_msg': 'Missing IP address', 'response_code': 0}
Notice that we didn't need to worry about either the parameter name or format (more on this in the next section). Also, whatever the function, the output is always returned as a pandas DataFrame.
\n
\n
For Data query functions you do need to worry about the parameter name
\n
Data query functions are slightly more complex than most other functions and specifically often support many parameters. Rather than try to guess which parameter you meant, we require you to be explicit about it.
\n
Before we can use a data query, we need to authenticate to the provider.
If you are not sure of the parameters required by the query you can use the built-in help
\n
\n
>>> Host.AzureSentinel.SecurityAlert_list_related_alerts? Signature: Host.AzureSentinel.SecurityAlert_list_related_alerts(*args,**kwargs)-> Union[pandas.core.frame.DataFrame, Any] Docstring: Retrieves list of alerts with a common host, account or process Parameters ---------- account_name: str (optional) The account name to find add_query_items: str (optional) Additional query clauses end: datetime (optional) Query end time host_name: str (optional) The hostname to find path_separator: str (optional) Path separator (default value is: \\\\) process_name: str (optional) ...
Shown below is a preview of a notebook tool that lets you browser around entities and their pivot functions, search for a function by keyword and view the help for that function. This is going to be released shortly.
\n
\n
>>> Pivot.browse()
\n
\n
\n
\n
Standardized way of calling Pivot functions
\n
\n
Due to various factors (historical, underlying data, developer laziness and forgetfulness, etc.) the functionality in MSTICPy can be inconsistent in the way it uses input parameters.
\n
Also, many functions will only accept inputs as a single value, or a list or a DataFrame or some unpredictable combination of these.
\n
Pivot functions allow you to largely forget about this - you can use the same function whether you have:
\n
\n
a single value
\n
a list of values (or any Python iterable, such as a tuple or even a generator function)
\n
a DataFrame with the input value in one of the columns.
\n
\n
Let's take an example.
\n
\n
Suppose we have a set of IP addresses pasted from somewhere that we want to use as input.
\n
We need to convert this into a Python data object of some sort.
\n
\n
To do this we can use another Pivot utility %%txt2df. This is a Jupyter/IPython magic function - to use it, just paste you data in a cell that you want to import into an empty. Use
\n
%%txt2df --help
\n
in an empty cell to see the full syntax.
\n
In the example below, we specify a comma separator, that the data has a headers row and to save the converted data as a DataFrame named \"ip_df\".
\n
\n
Warning if you specify the \"--name\" parameter, this will overwrite any existing variable of this name.
\n
\n
%%txt2df --sep , --headers --name ip_df idx, ip, type 0, 172.217.15.99, Public 1, 40.85.232.64, Public 2, 20.38.98.100, Public 3, 23.96.64.84, Public 4, 65.55.44.108, Public 5, 131.107.147.209, Public 6, 10.0.3.4, Private 7, 10.0.3.5, Private 8, 13.82.152.48, Public
\n
\n\n
\n
\n
\n
idx
\n
\n
\n
ip
\n
\n
\n
type
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
0
\n
\n
\n
172.217.15.99
\n
\n
\n
Public
\n
\n
\n
\n
\n
1
\n
\n
\n
1
\n
\n
\n
40.85.232.64
\n
\n
\n
Public
\n
\n
\n
\n
\n
2
\n
\n
\n
2
\n
\n
\n
20.38.98.100
\n
\n
\n
Public
\n
\n
\n
\n
\n
3
\n
\n
\n
3
\n
\n
\n
23.96.64.84
\n
\n
\n
Public
\n
\n
\n
\n
\n
4
\n
\n
\n
4
\n
\n
\n
65.55.44.108
\n
\n
\n
Public
\n
\n
\n
\n
\n
5
\n
\n
\n
5
\n
\n
\n
131.107.147.209
\n
\n
\n
Public
\n
\n
\n
\n
\n
6
\n
\n
\n
6
\n
\n
\n
10.0.3.4
\n
\n
\n
Private
\n
\n
\n
\n
\n
7
\n
\n
\n
7
\n
\n
\n
10.0.3.5
\n
\n
\n
Private
\n
\n
\n
\n
\n
8
\n
\n
\n
8
\n
\n
\n
13.82.152.48
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
For demonstration purposes, we'll also create a standard Python list from the \"ip\" column of the DataFrame.
If you recall the earlier example of get_ip_type, passing it a list or DataFrame doesn't result in anything useful.
\n
>>> get_ip_type(ip_list) ['172.217.15.99', '40.85.232.64', '20.38.98.100', '23.96.64.84', '65.55.44.108', '131.107.147.209', '10.0.3.4', '10.0.3.5', '13.82.152.48'] does not appear to be an IPv4 or IPv6 address 'Unspecified'
\n
\n
Pivot versions are (somewhat) agnostic to input data format
\n
However, the \"pivotized\" version can accept and correctly process a list.
\n
\n
>>> IpAddress.util.ip_type(ip_list)
\n
\n\n
\n
\n
\n
ip
\n
\n
\n
result
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
172.217.15.99
\n
\n
\n
Public
\n
\n
\n
\n
\n
1
\n
\n
\n
40.85.232.64
\n
\n
\n
Public
\n
\n
\n
\n
\n
2
\n
\n
\n
20.38.98.100
\n
\n
\n
Public
\n
\n
\n
\n
\n
3
\n
\n
\n
23.96.64.84
\n
\n
\n
Public
\n
\n
\n
\n
\n
4
\n
\n
\n
65.55.44.108
\n
\n
\n
Public
\n
\n
\n
\n
\n
5
\n
\n
\n
131.107.147.209
\n
\n
\n
Public
\n
\n
\n
\n
\n
6
\n
\n
\n
10.0.3.4
\n
\n
\n
Private
\n
\n
\n
\n
\n
7
\n
\n
\n
10.0.3.5
\n
\n
\n
Private
\n
\n
\n
\n
\n
8
\n
\n
\n
13.82.152.48
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
In the case of a DataFrame, we have to tell the function the name of the column that contains the input data.
\n
\n
>>> IpAddress.util.whois(ip_df) # won't work! --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-32-debf57d805c7> in <module> ... 173 input_df, input_column, param_dict = _create_input_df( --> 174 input_value, pivot_reg, parent_kwargs=kwargs 175 ) ... KeyError: (\"'ip_column' is not in the input dataframe\", 'Please specify the column when calling the function. You can use one of the parameter names for this:', ['column', 'input_column', 'input_col', 'src_column', 'src_col'])
Note: for most functions you can ignore the parameter name and just specify it as a positional parameter. You can also use the original parameter name of the underlying function or the placeholder name \"value\".
You can also pass an entity instance of an entity as a input parameter. The pivot code knows which attribute or attributes of an entity will provider the input value.
Iterable/DataFrame inputs and single-value functions
\n
Many of the underlying functions only accept single values as inputs. Examples of these are the data query functions - typically they expect a single host name, IP address, etc.
\n
Pivot knows about the type of parameters that the function accepts. It will adjust the input to match the expectations of the underlying function. If a list or DataFrame is passed as input to a single-value function Pivot will split the input and call the function once for each value. It then combines the output into a single DataFrame before returning the results.
\n
You can read a bit more about how this is done in the Appendix - \"how do pivot wrappers work?\"
\n
\n
Data queries - where does the time range come from?
\n
The Pivot class has a built-in time range, which is used by default for all queries. Don't worry - you can change it easily.
Note: \"Pivot.current\" gives you access to the last created instance of the Pivot class - if you've created multiple instances of Pivot (which you rarely need to do), you can always get to the last one you created using this class attribute.
\n
\n
You can edit the time range interactively
\n
Pivot.current.edit_query_time()
\n
Or by setting the timespan property directly.
\n
>>> from msticpy.common.timespan import TimeSpan >>> # TimeSpan accepts datetimes or datestrings >>> timespan = TimeSpan(start=\"02/01/2021\", end=\"02/15/2021\") >>> Pivot.current.timespan = timespan TimeStamp(start=2021-02-01 00:00:00, end=2021-02-15 00:00:00, period=-14 days +00:00:00)
\n
In an upcoming release there is also a convenience function for setting the time directly with Python datetimes or date strings.
The Pivot layer will pass any unused keyword parameters to the underlying function. This does not usually apply to positional parameters - if you want parameters to get to the function, you have to name them explicitly. In this example the add_query_items parameter is passed to the underlying query function
\n
>>> entities.Host.AzureSentinel.SecurityEvent_list_host_logons( host_name=\"victimPc\", add_query_items=\"| summarize count() by LogonType\" )
\n
\n\n
\n
\n
\n
LogonType
\n
\n
\n
count_
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
5
\n
\n
\n
27492
\n
\n
\n
\n
\n
1
\n
\n
\n
4
\n
\n
\n
12597
\n
\n
\n
\n
\n
2
\n
\n
\n
3
\n
\n
\n
6936
\n
\n
\n
\n
\n
3
\n
\n
\n
2
\n
\n
\n
173
\n
\n
\n
\n
\n
4
\n
\n
\n
10
\n
\n
\n
58
\n
\n
\n
\n
\n
5
\n
\n
\n
9
\n
\n
\n
8
\n
\n
\n
\n
\n
6
\n
\n
\n
0
\n
\n
\n
19
\n
\n
\n
\n
\n
7
\n
\n
\n
11
\n
\n
\n
1
\n
\n
\n\n
\n
\n
\n
Pivot Pipelines
\n
\n
Because all pivot functions accept DataFrames as input and produce DataFrames as output, it means that it is possible to chain pivot functions into a pipeline.
\n
\n
Joining input to output
\n
You can join the input to the output. This usually only makes sense when the input is a DataFrame. It lets you keep the previously accumulated results and tag on the additional columns produced by the pivot function you are calling.
\n
\n
The join parameter supports \"inner\", \"left\", \"right\" and \"outer\" joins (be careful with the latter though!) See pivot joins documentation for more details.
\n
\n
Although joining is useful in pipelines you can use it on any function whether in a pipeline or not. In this example you can see that the idx, ip and type columns have been carried over from the source DataFrame and joined with the output.
Here is an example of using it to call four pivot functions, each using the output of the previous function as input and using the join parameter to accumulate the results from each stage.
The whole thing is surrounded by a pair of parentheses - this is just to let us split the whole expression over multiple lines without Python complaining.
\n
Next we have ips_df - this is just the starting DataFrame, our input data.
\n
Next we call the mp_pivot.run() accessor method on this dataframe. We pass it the pivot function that we want to run (IpAddress.util.ip_type) and the input column name (IP). This column name is the column in ips_df where our input IP addresses are. We've also specified an join type of \"inner\". In this case the join type doesn't really matter since we know we get exactly one output row for every input row.
\n
We're using the pandas query function to filter out unwanted entries from the previous stage. In this case we only want \"Public\" IP addresses. This illustrates that you can intersperse standard pandas functions in the same pipeline. We could have also added a column selector expression ([[\"col1\", \"col2\"...]]), for example, if we wanted to filter the columns passed to the next stage
\n
We are calling a further pivot function - whois. Remember the \"column\" parameter always refers to the input column, i.e. the column from previous stage that we want to use in this stage.
\n
We are calling geoloc_mm to get geo location details joining with a \"left\" join - this preserves the input data rows and adds null columns in any cases where the pivot function returned no result.
\n
Is the same as 6 except the called function is a data query to see if we have any alerts that contain these IP addresses. Remember, in the case of data queries we have to name the specific query parameter that we want the input to go to. In this case, each row value in the ip column from the previous stage will be sent to the query.
\n
Finally we close the parentheses to form a valid Python expression. The whole expression returns a DataFrame so we can add further pandas operations here (like .head(5) shown here).
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
91d806d3-6b6f-4e5c-a78f-e674d602be51
\n
\n
\n
625ff9af-dddc-0cf8-9d4b-e79067fa2e71
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n
\n
\n
1
\n
\n
\n
8ecf8077-cf51-4820-aadd-14040956f35d
\n
\n
\n
2020-12-23 14:08:12+00:00
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Medium
\n
\n
\n
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
173063c4-10dd-4dd2-9e4f-ec5ed596ec54
\n
\n
\n
c977f904-ab30-d57e-986f-9d6ebf72771b
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n
\n
\n
2
\n
\n
\n
8ecf8077-cf51-4820-aadd-14040956f35d
\n
\n
\n
2020-12-23 14:08:12+00:00
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Medium
\n
\n
\n
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
58b2cda2-11c6-42b8-b6f1-72751cad8f38
\n
\n
\n
9ee547e4-cba1-47d1-e1f9-87247b693a52
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n\n
\n
\n
Other pipeline functions
\n
In addition to run, the mp_pivot accessor also has the following functions:
\n
\n
display - this simply displays the data at the point called in the pipeline. You can add an optional title, filtering and the number or rows to display
\n
tee - this forks a copy of the DataFrame at the point it is called in the pipeline. It will assign the forked copy to the name given in the var_name parameter. If there is an existing variable of the same name it will not overwrite it unless you add the clobber=True parameter.
\n
\n
In both cases the pipelined data is passed through unchanged.
Use of these is shown below in this partial pipeline.
\n
... .mp_pivot.run(IpAddress.util.geoloc_mm, column=\"ip\", join=\"left\") .mp_pivot.display(title=\"Geo Lookup\", cols=[\"IP\", \"City\"]) # << display an intermediate result .mp_pivot.tee(var_name=\"geoip_df\", clobber=True) # << save a copy called 'geoip_df' .mp_pivot.run(IpAddress.AzureSentinel.SecurityAlert_list_alerts_for_ip, source_ip_list=\"ip\", join=\"left\")
\n
\n
In the next release we've also implemented:
\n
\n
tee_exec - this executes a function on a forked copy of the DataFrame The function must be a pandas function or custom accessor. A good example of the use of this might be creating a plot or summary table to display partway through the pipeline.
\n
\n
\n
Extending Pivot - adding your own (or someone else's) functions
\n
\n
You can add pivot functions of your own. You need to supply:
\n
\n
the function
\n
some metadata that describes where the function can be found and how the function works
The current version of Pivot doesn't let you add functions defined inline (i.e. written in the notebook itself) but this will be possible in the forthcoming release.
\n
\n
Let's create a function in a Python module my_module.py. We can do this using the %%write_file magic function and running the cell.
\n
%%writefile my_module.py \"\"\"Upper-case and hash\"\"\" from hashlib import md5
def my_func(input: str): md5_hash = \"-\".join(hex(b)[2:] for b in md5(input.encode(\"utf-8\")).digest()) return { \"Title\": input.upper(), \"Hash\": md5_hash }
\n
We also need to create a YAML definition file for our pivot function. Again we can use %%write_file to create a local file in the current directory. We need to tell Pivot
\n
\n
the name of the function and source module,
\n
the name of the container that the function will appear in,
\n
the input type expected by the function (\"value\", \"list\" or \"dataframe\")
\n
which entities to add the pivot to, along with a corresponding attribute of the entity. (The attribute is used in cases where you are passing an instance of an entity itself as an input parameter - if in doubt just use any valid attribute of the entity).
\n
The name of the input attribute of the underlying function.
Now we can register the function we created as a pivot function.
\n
>>> from msticpy.datamodel.pivot_register_reader import register_pivots >>> register_pivots(\"my_func.yml\")
\n
An then run it.
\n
>>> Host.cyber.upper_hash_name(\"host_name\")
\n
\n\n
\n
\n
\n
Title
\n
\n
\n
Hash
\n
\n
\n
input
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
HOST_NAME
\n
\n
\n
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
\n
\n
\n
host_name
\n
\n
\n\n
\n
\n
In the next release, this will be available as a simple function that can be used to add a function defined in the notebook as shown here.
\n
\n
from hashlib import md5
def my_func2(input: str): md5_hash = \"-\".join(hex(b)[2:] for b in md5(input.encode(\"utf-8\")).digest()) return { \"Title\": input.upper(), \"Hash\": md5_hash }
Pivot.add_pivot_function( func=my_func2, container=\"cyber\", # which container it will appear in on the entity input_type=\"value\", entity_map={\"Host\": \"HostName\"}, func_input_value_arg=\"input\", func_new_name=\"il_upper_hash_name\", )
Host.cyber.il_upper_hash_name(\"host_name\")
\n
\n\n
\n
\n
\n
Title
\n
\n
\n
Hash
\n
\n
\n
input
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
HOST_NAME
\n
\n
\n
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
\n
\n
\n
host_name
\n
\n
\n\n
\n
\n
\n
Conclusion
\n
\n
We've taken a short tour through the MSTICPy Pivot functions, looking at how they make the functionality in MSTICPy easier to discover and use.
\n
\n
I'm particularly excited about the pipeline functionality. In the next release we're going to make it possible to define reusable pipelines in configuration files and execute them with a single function call. This should help streamline some common patterns in notebooks for Cyber hunting and investigation.
In Python you can create functions that return other functions. This is called wrapping the function.
\n
It allows the outer function to do additional things to the input parameters and the return value of the inner function.
\n
\n
Take this simple function that just applies proper capitalization to an input string.
\n
def print_me(arg): print(arg.capitalize())
print_me(\"hello\")
\n
Hello
\n
\n
If we try to pass a list to this function we get an expected exception since the function only knows how to process a string
\n
print_me([\"hello\", \"world\"]) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-36-94b3e61eb86f> in <module> … AttributeError: 'list' object has no attribute 'capitalize'
\n
We could create a wrapper function that checked the input and iterated over the individual items if arg is a list. The works but we don't want to have to do this for every function that we want to have flexible input!
\n
def print_me_list(arg): if isinstance(arg, list): for item in arg: print_me(item) else: print_me(arg)
In the example below, the outer function dont_care_func defines an inner function - list_or_str - and then returns this function. The inner function list_or_str is what implements the same \"is-this-a-string-or-list\" logic that we saw in the previous example. Crucially though, it isn't hard-coded to call print_me but calls whatever function is passed (the func parameter) to it from the outer function dont_care_func.
\n
\n
# Our magic wrapper def dont_care_func(func):
def list_or_str(arg): if isinstance(arg, list): for item in arg: func(item) else: func(arg) return list_or_str
\n
\n
How do we use this?
\n
We simply pass the function that we want to wrap to dont_care_func. Recall, that this function just returns an instance of the inner function. In this case the value func will have been replaced by the actual function print_me.
\n
print_stuff = dont_care_func(print_me)
\n
Now we have a wrapped version of print_me that can handle different types of input. Magic!
You can also add other functions from 3rd party Python packages or ones you write yourself as pivot functions.
\n
\n
Terminology
\n
Before we get into things let's clear up a few terms.
\n
\n
Entities - These are Python classes that represent real-world objects commonly encountered in CyberSec investigations and hunting. E.g., Host, URL, IP Address, Account, etc.
\n
\n
Pivoting - This comes from the common practice in CyberSec investigations of navigating from one suspect entity to another. E.g., you might start with an alert identifying a potentially malicious IP Address, from there you 'pivot' to see which hosts or accounts were communicating with that address. From there you might pivot again to look at processes running on the host or Office activity for the account.
\n
\n
Background reading
\n
This article is available in Notebook form so that you can try out the examples. [TODO]
\n
There is also full documentation of the Pivot functionality on our ReadtheDocs page.
\n
\n
Life before pivot functions
\n
\n
Before Pivot functions your ability to use the various bits of functionality in MSTICPy was always bounded by your knowledge of where a certain function was (or your enthusiasm for reading the docs).
\n
For example, suppose you had an IP address that you wanted to do some simple enrichment on.
\n
ip_addr=\"20.72.193.242\"
\n
First, you'd need to locate and import the functions. There might also be (as in the GeoIPLiteLookup class) some initialization step you'd need to do before using the functionality.
Next you might have to check the help for each function to work it parameters.
\n
>>> help(get_ip_type) Help on function get_ip_type in module msticpy.sectools.ip_utils: get_ip_type(ip: str = None, ip_str: str = None) -> str Validate value is an IP address and deteremine IPType category. ...
At which point you'd discover that the output from each function was somewhat raw and it would take a bit more work if you wanted to combine it in any way (say in a single table).
\n
\n
In the rest of the article we'll show you how Pivot functions make it easier to discover data and enrichment functions. We'll also show how pivot functions bring standardization and handle different types of input (including lists and DataFrames) and finally, how the standardized output lets you chain multiple pivot functions together into re-usable pipelines of functionality.
\n
\n
Getting started with pivot functions
\n
\n
Let's get started with how to use Pivot functions.
\n
Typically, we use MSTICPy's init_notebook function at the start of any notebook. This handles checking versions and importing some commonly-used packages and modules (both MSTICPy and 3rd party packages like pandas).
Then there are a couple of preliminary steps needed before you can use pivot functions. The main one is loading the Pivot class.
\n
\n
Pivot functions are added to the entities dynamically by the Pivot class. The Pivot class will try to discover relevant functions from queries, Threat Intel providers and various utility functions.
\n
\n
In some cases, notably data queries, the data query functions are themselves created dynamically, so these need to be loaded before you create the Pivot class. (You can always create a new instance of this class, which forces re-discovery, so don't worry if mess up the order of things).
\n
\n
Note in most cases we don't need to connect/authenticate to a data provider prior to loading Pivot.
\n
\n
Let's load our data query provider for AzureSentinel.
Pivot searches through the current objects defined in the Python/notebook namespace to find provider objects that it will use to create the pivot functions. This is most relevant for QueryProviders - when you create a Pivot class instance it will find and use the relevant queries from the az_provider object that we created in the previous step. In most other cases (like GeoIP and ThreatIntel providers, it will create new ones if it can't find existing ones).
\n
\n
Easy discovery of functionality
\n
\n
Find the entity name you need
\n
The simplest way to do this is simply enumerate (using Python dir() function) the contents of the MSTICPy entities sub-package. This should have already been imported by the init_notebook function that we ran earlier.
\n
The items at the beginning of the list with proper capitalization are the entities.
We're going to make this a little more elegant in a forthcoming update with this helper function.
\n
>>> entities.find_entity(\"ip\") Match found 'IpAddress'msticpy.datamodel.entities.ip_address.IpAddress
\n
Listing pivot functions available for an entity
\n
Note you can always address an entity using its qualified path, e.g. \"entities.IpAddress\" but if you are going to use one or two entities a lot, it will save a bit of typing if you import them explicitly.
Once you have the entity loaded, you can use the get_pivot_list() function to see which pivot functions are available for it. The example below has been abbreviated for space reasons.
Some of the function names are a little unwieldy but, in many cases, this is necessary to avoid name collisions. You will notice from the list that the functions are grouped into containers: \"AzureSentinel\", \"ti\" and \"util\" in the above example.
\n
\n
Although this makes the function name even longer, we thought that this helped to keep related functionality together - so you don't get a TI lookup function, when you thought you were running a query.
\n
Fortunately, Jupyter notebooks/IPython support tab completion so you should not normally have to remember these names.
\n
\n
\n
\n
The containers (\"AzureSentinel\", \"util\", etc.) are also callable functions - they just return the list of functions they contain.
\n
>>> IpAddress.util() whois functionip_type functionip_rev_resolve functiongeoloc_mm functiongeoloc_ips function
\n
Now we're ready to run any of the functions for this entity (we take the same initial examples from the \"Life before pivot functions\" plus a few more).
\n
\n
>>> IpAddress.util.ip_type(ip_addr)
\n
\n\n
\n
\n
\n
ip
\n
\n
\n
result
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
>>> IpAddress.util.whois(ip_addr)
\n
\n\n
\n
\n
\n
asn
\n
\n
\n
asn_cidr
\n
\n
\n
asn_country_code
\n
\n
\n
asn_date
\n
\n
\n
asn_description
\n
\n
\n
asn_registry
\n
\n
\n
nets
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
8075
\n
\n
\n
20.64.0.0/10
\n
\n
\n
US
\n
\n
\n
2017-10-18
\n
\n
\n
MICROSOFT-CORP-MSN-AS-BLOCK, US
\n
\n
\n
arin
\n
\n
\n
[{'cidr': '20.128.0.0/16, 20.48, ...
\n
\n
\n\n
\n
\n
>>> IpAddress.util.ip_rev_resolve(ip_addr)
\n
\n\n
\n
\n
\n
qname
\n
\n
\n
rdtype
\n
\n
\n
response
\n
\n
\n
ip_address
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
PTR
\n
\n
\n
The DNS query name does not exist: 20.72.193.242.
\n
\n
\n
20.72.193.242
\n
\n
\n\n
\n
\n
>>> IpAddress.util.geoloc_mm(ip_addr)
\n
\n\n
\n
\n
\n
CountryCode
\n
\n
\n
CountryName
\n
\n
\n
State
\n
\n
\n
City
\n
\n
\n
Longitude
\n
\n
\n
Latitude
\n
\n
\n
Asn
\n
\n
\n
edges
\n
\n
\n
Type
\n
\n
\n
AdditionalData
\n
\n
\n
IpAddress
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
US
\n
\n
\n
United States
\n
\n
\n
Washington
\n
\n
\n
None
\n
\n
\n
-122.3412
\n
\n
\n
47.6032
\n
\n
\n
None
\n
\n
\n
{}
\n
\n
\n
geolocation
\n
\n
\n
{}
\n
\n
\n
20.72.193.242
\n
\n
\n\n
\n
\n
>>> IpAddress.ti.lookup_ip(ip_addr)
\n
\n\n
\n
\n
\n
Ioc
\n
\n
\n
IocType
\n
\n
\n
SafeIoc
\n
\n
\n
QuerySubtype
\n
\n
\n
Provider
\n
\n
\n
Result
\n
\n
\n
Severity
\n
\n
\n
Details
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
ipv4
\n
\n
\n
20.72.193.242
\n
\n
\n
None
\n
\n
\n
Tor
\n
\n
\n
True
\n
\n
\n
information
\n
\n
\n
Not found.
\n
\n
\n
\n
\n
0
\n
\n
\n
20.72.193.242
\n
\n
\n
ipv4
\n
\n
\n
20.72.193.242
\n
\n
\n
None
\n
\n
\n
VirusTotal
\n
\n
\n
True
\n
\n
\n
unknown
\n
\n
\n
{'verbose_msg': 'Missing IP address', 'response_code': 0}
Notice that we didn't need to worry about either the parameter name or format (more on this in the next section). Also, whatever the function, the output is always returned as a pandas DataFrame.
\n
\n
For Data query functions you do need to worry about the parameter name
\n
Data query functions are slightly more complex than most other functions and specifically often support many parameters. Rather than try to guess which parameter you meant, we require you to be explicit about it.
\n
Before we can use a data query, we need to authenticate to the provider.
If you are not sure of the parameters required by the query you can use the built-in help
\n
\n
>>> Host.AzureSentinel.SecurityAlert_list_related_alerts? Signature: Host.AzureSentinel.SecurityAlert_list_related_alerts(*args,**kwargs)-> Union[pandas.core.frame.DataFrame, Any] Docstring: Retrieves list of alerts with a common host, account or process Parameters ---------- account_name: str (optional) The account name to find add_query_items: str (optional) Additional query clauses end: datetime (optional) Query end time host_name: str (optional) The hostname to find path_separator: str (optional) Path separator (default value is: \\\\) process_name: str (optional) ...
Shown below is a preview of a notebook tool that lets you browser around entities and their pivot functions, search for a function by keyword and view the help for that function. This is going to be released shortly.
\n
\n
>>> Pivot.browse()
\n
\n
\n
\n
Standardized way of calling Pivot functions
\n
\n
Due to various factors (historical, underlying data, developer laziness and forgetfulness, etc.) the functionality in MSTICPy can be inconsistent in the way it uses input parameters.
\n
Also, many functions will only accept inputs as a single value, or a list or a DataFrame or some unpredictable combination of these.
\n
Pivot functions allow you to largely forget about this - you can use the same function whether you have:
\n
\n
a single value
\n
a list of values (or any Python iterable, such as a tuple or even a generator function)
\n
a DataFrame with the input value in one of the columns.
\n
\n
Let's take an example.
\n
\n
Suppose we have a set of IP addresses pasted from somewhere that we want to use as input.
\n
We need to convert this into a Python data object of some sort.
\n
\n
To do this we can use another Pivot utility %%txt2df. This is a Jupyter/IPython magic function - to use it, just paste you data in a cell that you want to import into an empty. Use
\n
%%txt2df --help
\n
in an empty cell to see the full syntax.
\n
In the example below, we specify a comma separator, that the data has a headers row and to save the converted data as a DataFrame named \"ip_df\".
\n
\n
Warning if you specify the \"--name\" parameter, this will overwrite any existing variable of this name.
\n
\n
%%txt2df --sep , --headers --name ip_df idx, ip, type 0, 172.217.15.99, Public 1, 40.85.232.64, Public 2, 20.38.98.100, Public 3, 23.96.64.84, Public 4, 65.55.44.108, Public 5, 131.107.147.209, Public 6, 10.0.3.4, Private 7, 10.0.3.5, Private 8, 13.82.152.48, Public
\n
\n\n
\n
\n
\n
idx
\n
\n
\n
ip
\n
\n
\n
type
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
0
\n
\n
\n
172.217.15.99
\n
\n
\n
Public
\n
\n
\n
\n
\n
1
\n
\n
\n
1
\n
\n
\n
40.85.232.64
\n
\n
\n
Public
\n
\n
\n
\n
\n
2
\n
\n
\n
2
\n
\n
\n
20.38.98.100
\n
\n
\n
Public
\n
\n
\n
\n
\n
3
\n
\n
\n
3
\n
\n
\n
23.96.64.84
\n
\n
\n
Public
\n
\n
\n
\n
\n
4
\n
\n
\n
4
\n
\n
\n
65.55.44.108
\n
\n
\n
Public
\n
\n
\n
\n
\n
5
\n
\n
\n
5
\n
\n
\n
131.107.147.209
\n
\n
\n
Public
\n
\n
\n
\n
\n
6
\n
\n
\n
6
\n
\n
\n
10.0.3.4
\n
\n
\n
Private
\n
\n
\n
\n
\n
7
\n
\n
\n
7
\n
\n
\n
10.0.3.5
\n
\n
\n
Private
\n
\n
\n
\n
\n
8
\n
\n
\n
8
\n
\n
\n
13.82.152.48
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
For demonstration purposes, we'll also create a standard Python list from the \"ip\" column of the DataFrame.
If you recall the earlier example of get_ip_type, passing it a list or DataFrame doesn't result in anything useful.
\n
>>> get_ip_type(ip_list) ['172.217.15.99', '40.85.232.64', '20.38.98.100', '23.96.64.84', '65.55.44.108', '131.107.147.209', '10.0.3.4', '10.0.3.5', '13.82.152.48'] does not appear to be an IPv4 or IPv6 address 'Unspecified'
\n
\n
Pivot versions are (somewhat) agnostic to input data format
\n
However, the \"pivotized\" version can accept and correctly process a list.
\n
\n
>>> IpAddress.util.ip_type(ip_list)
\n
\n\n
\n
\n
\n
ip
\n
\n
\n
result
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
172.217.15.99
\n
\n
\n
Public
\n
\n
\n
\n
\n
1
\n
\n
\n
40.85.232.64
\n
\n
\n
Public
\n
\n
\n
\n
\n
2
\n
\n
\n
20.38.98.100
\n
\n
\n
Public
\n
\n
\n
\n
\n
3
\n
\n
\n
23.96.64.84
\n
\n
\n
Public
\n
\n
\n
\n
\n
4
\n
\n
\n
65.55.44.108
\n
\n
\n
Public
\n
\n
\n
\n
\n
5
\n
\n
\n
131.107.147.209
\n
\n
\n
Public
\n
\n
\n
\n
\n
6
\n
\n
\n
10.0.3.4
\n
\n
\n
Private
\n
\n
\n
\n
\n
7
\n
\n
\n
10.0.3.5
\n
\n
\n
Private
\n
\n
\n
\n
\n
8
\n
\n
\n
13.82.152.48
\n
\n
\n
Public
\n
\n
\n\n
\n
\n
In the case of a DataFrame, we have to tell the function the name of the column that contains the input data.
\n
\n
>>> IpAddress.util.whois(ip_df) # won't work! --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-32-debf57d805c7> in <module> ... 173 input_df, input_column, param_dict = _create_input_df( --> 174 input_value, pivot_reg, parent_kwargs=kwargs 175 ) ... KeyError: (\"'ip_column' is not in the input dataframe\", 'Please specify the column when calling the function. You can use one of the parameter names for this:', ['column', 'input_column', 'input_col', 'src_column', 'src_col'])
Note: for most functions you can ignore the parameter name and just specify it as a positional parameter. You can also use the original parameter name of the underlying function or the placeholder name \"value\".
You can also pass an entity instance of an entity as a input parameter. The pivot code knows which attribute or attributes of an entity will provider the input value.
Iterable/DataFrame inputs and single-value functions
\n
Many of the underlying functions only accept single values as inputs. Examples of these are the data query functions - typically they expect a single host name, IP address, etc.
\n
Pivot knows about the type of parameters that the function accepts. It will adjust the input to match the expectations of the underlying function. If a list or DataFrame is passed as input to a single-value function Pivot will split the input and call the function once for each value. It then combines the output into a single DataFrame before returning the results.
\n
You can read a bit more about how this is done in the Appendix - \"how do pivot wrappers work?\"
\n
\n
Data queries - where does the time range come from?
\n
The Pivot class has a built-in time range, which is used by default for all queries. Don't worry - you can change it easily.
Note: \"Pivot.current\" gives you access to the last created instance of the Pivot class - if you've created multiple instances of Pivot (which you rarely need to do), you can always get to the last one you created using this class attribute.
\n
\n
You can edit the time range interactively
\n
Pivot.current.edit_query_time()
\n
Or by setting the timespan property directly.
\n
>>> from msticpy.common.timespan import TimeSpan >>> # TimeSpan accepts datetimes or datestrings >>> timespan = TimeSpan(start=\"02/01/2021\", end=\"02/15/2021\") >>> Pivot.current.timespan = timespan TimeStamp(start=2021-02-01 00:00:00, end=2021-02-15 00:00:00, period=-14 days +00:00:00)
\n
In an upcoming release there is also a convenience function for setting the time directly with Python datetimes or date strings.
The Pivot layer will pass any unused keyword parameters to the underlying function. This does not usually apply to positional parameters - if you want parameters to get to the function, you have to name them explicitly. In this example the add_query_items parameter is passed to the underlying query function
\n
>>> entities.Host.AzureSentinel.SecurityEvent_list_host_logons( host_name=\"victimPc\", add_query_items=\"| summarize count() by LogonType\" )
\n
\n\n
\n
\n
\n
LogonType
\n
\n
\n
count_
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
5
\n
\n
\n
27492
\n
\n
\n
\n
\n
1
\n
\n
\n
4
\n
\n
\n
12597
\n
\n
\n
\n
\n
2
\n
\n
\n
3
\n
\n
\n
6936
\n
\n
\n
\n
\n
3
\n
\n
\n
2
\n
\n
\n
173
\n
\n
\n
\n
\n
4
\n
\n
\n
10
\n
\n
\n
58
\n
\n
\n
\n
\n
5
\n
\n
\n
9
\n
\n
\n
8
\n
\n
\n
\n
\n
6
\n
\n
\n
0
\n
\n
\n
19
\n
\n
\n
\n
\n
7
\n
\n
\n
11
\n
\n
\n
1
\n
\n
\n\n
\n
\n
\n
Pivot Pipelines
\n
\n
Because all pivot functions accept DataFrames as input and produce DataFrames as output, it means that it is possible to chain pivot functions into a pipeline.
\n
\n
Joining input to output
\n
You can join the input to the output. This usually only makes sense when the input is a DataFrame. It lets you keep the previously accumulated results and tag on the additional columns produced by the pivot function you are calling.
\n
\n
The join parameter supports \"inner\", \"left\", \"right\" and \"outer\" joins (be careful with the latter though!) See pivot joins documentation for more details.
\n
\n
Although joining is useful in pipelines you can use it on any function whether in a pipeline or not. In this example you can see that the idx, ip and type columns have been carried over from the source DataFrame and joined with the output.
Here is an example of using it to call four pivot functions, each using the output of the previous function as input and using the join parameter to accumulate the results from each stage.
The whole thing is surrounded by a pair of parentheses - this is just to let us split the whole expression over multiple lines without Python complaining.
\n
Next we have ips_df - this is just the starting DataFrame, our input data.
\n
Next we call the mp_pivot.run() accessor method on this dataframe. We pass it the pivot function that we want to run (IpAddress.util.ip_type) and the input column name (IP). This column name is the column in ips_df where our input IP addresses are. We've also specified an join type of \"inner\". In this case the join type doesn't really matter since we know we get exactly one output row for every input row.
\n
We're using the pandas query function to filter out unwanted entries from the previous stage. In this case we only want \"Public\" IP addresses. This illustrates that you can intersperse standard pandas functions in the same pipeline. We could have also added a column selector expression ([[\"col1\", \"col2\"...]]), for example, if we wanted to filter the columns passed to the next stage
\n
We are calling a further pivot function - whois. Remember the \"column\" parameter always refers to the input column, i.e. the column from previous stage that we want to use in this stage.
\n
We are calling geoloc_mm to get geo location details joining with a \"left\" join - this preserves the input data rows and adds null columns in any cases where the pivot function returned no result.
\n
Is the same as 6 except the called function is a data query to see if we have any alerts that contain these IP addresses. Remember, in the case of data queries we have to name the specific query parameter that we want the input to go to. In this case, each row value in the ip column from the previous stage will be sent to the query.
\n
Finally we close the parentheses to form a valid Python expression. The whole expression returns a DataFrame so we can add further pandas operations here (like .head(5) shown here).
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
91d806d3-6b6f-4e5c-a78f-e674d602be51
\n
\n
\n
625ff9af-dddc-0cf8-9d4b-e79067fa2e71
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n
\n
\n
1
\n
\n
\n
8ecf8077-cf51-4820-aadd-14040956f35d
\n
\n
\n
2020-12-23 14:08:12+00:00
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Medium
\n
\n
\n
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
173063c4-10dd-4dd2-9e4f-ec5ed596ec54
\n
\n
\n
c977f904-ab30-d57e-986f-9d6ebf72771b
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n
\n
\n
2
\n
\n
\n
8ecf8077-cf51-4820-aadd-14040956f35d
\n
\n
\n
2020-12-23 14:08:12+00:00
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Microsoft Threat Intelligence Analytics
\n
\n
\n
Medium
\n
\n
\n
Microsoft threat intelligence analytic has detected Blocked communication to a known WatchList d...
\n
\n
\n
Threat Intelligence Alerts
\n
\n
\n
Microsoft
\n
\n
\n
58b2cda2-11c6-42b8-b6f1-72751cad8f38
\n
\n
\n
9ee547e4-cba1-47d1-e1f9-87247b693a52
\n
\n
\n
ThreatIntelligence
\n
\n
\n
83
\n
\n
\n\n
\n
\n
Other pipeline functions
\n
In addition to run, the mp_pivot accessor also has the following functions:
\n
\n
display - this simply displays the data at the point called in the pipeline. You can add an optional title, filtering and the number or rows to display
\n
tee - this forks a copy of the DataFrame at the point it is called in the pipeline. It will assign the forked copy to the name given in the var_name parameter. If there is an existing variable of the same name it will not overwrite it unless you add the clobber=True parameter.
\n
\n
In both cases the pipelined data is passed through unchanged.
Use of these is shown below in this partial pipeline.
\n
... .mp_pivot.run(IpAddress.util.geoloc_mm, column=\"ip\", join=\"left\") .mp_pivot.display(title=\"Geo Lookup\", cols=[\"IP\", \"City\"]) # << display an intermediate result .mp_pivot.tee(var_name=\"geoip_df\", clobber=True) # << save a copy called 'geoip_df' .mp_pivot.run(IpAddress.AzureSentinel.SecurityAlert_list_alerts_for_ip, source_ip_list=\"ip\", join=\"left\")
\n
\n
In the next release we've also implemented:
\n
\n
tee_exec - this executes a function on a forked copy of the DataFrame The function must be a pandas function or custom accessor. A good example of the use of this might be creating a plot or summary table to display partway through the pipeline.
\n
\n
\n
Extending Pivot - adding your own (or someone else's) functions
\n
\n
You can add pivot functions of your own. You need to supply:
\n
\n
the function
\n
some metadata that describes where the function can be found and how the function works
The current version of Pivot doesn't let you add functions defined inline (i.e. written in the notebook itself) but this will be possible in the forthcoming release.
\n
\n
Let's create a function in a Python module my_module.py. We can do this using the %%write_file magic function and running the cell.
\n
%%writefile my_module.py \"\"\"Upper-case and hash\"\"\" from hashlib import md5
def my_func(input: str): md5_hash = \"-\".join(hex(b)[2:] for b in md5(input.encode(\"utf-8\")).digest()) return { \"Title\": input.upper(), \"Hash\": md5_hash }
\n
We also need to create a YAML definition file for our pivot function. Again we can use %%write_file to create a local file in the current directory. We need to tell Pivot
\n
\n
the name of the function and source module,
\n
the name of the container that the function will appear in,
\n
the input type expected by the function (\"value\", \"list\" or \"dataframe\")
\n
which entities to add the pivot to, along with a corresponding attribute of the entity. (The attribute is used in cases where you are passing an instance of an entity itself as an input parameter - if in doubt just use any valid attribute of the entity).
\n
The name of the input attribute of the underlying function.
Now we can register the function we created as a pivot function.
\n
>>> from msticpy.datamodel.pivot_register_reader import register_pivots >>> register_pivots(\"my_func.yml\")
\n
An then run it.
\n
>>> Host.cyber.upper_hash_name(\"host_name\")
\n
\n\n
\n
\n
\n
Title
\n
\n
\n
Hash
\n
\n
\n
input
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
HOST_NAME
\n
\n
\n
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
\n
\n
\n
host_name
\n
\n
\n\n
\n
\n
In the next release, this will be available as a simple function that can be used to add a function defined in the notebook as shown here.
\n
\n
from hashlib import md5
def my_func2(input: str): md5_hash = \"-\".join(hex(b)[2:] for b in md5(input.encode(\"utf-8\")).digest()) return { \"Title\": input.upper(), \"Hash\": md5_hash }
Pivot.add_pivot_function( func=my_func2, container=\"cyber\", # which container it will appear in on the entity input_type=\"value\", entity_map={\"Host\": \"HostName\"}, func_input_value_arg=\"input\", func_new_name=\"il_upper_hash_name\", )
Host.cyber.il_upper_hash_name(\"host_name\")
\n
\n\n
\n
\n
\n
Title
\n
\n
\n
Hash
\n
\n
\n
input
\n
\n
\n\n\n
\n
\n
0
\n
\n
\n
HOST_NAME
\n
\n
\n
5d-41-40-2a-bc-4b-2a-76-b9-71-9d-91-10-17-c5-92
\n
\n
\n
host_name
\n
\n
\n\n
\n
\n
\n
Conclusion
\n
\n
We've taken a short tour through the MSTICPy Pivot functions, looking at how they make the functionality in MSTICPy easier to discover and use.
\n
\n
I'm particularly excited about the pipeline functionality. In the next release we're going to make it possible to define reusable pipelines in configuration files and execute them with a single function call. This should help streamline some common patterns in notebooks for Cyber hunting and investigation.
In Python you can create functions that return other functions. This is called wrapping the function.
\n
It allows the outer function to do additional things to the input parameters and the return value of the inner function.
\n
\n
Take this simple function that just applies proper capitalization to an input string.
\n
def print_me(arg): print(arg.capitalize())
print_me(\"hello\")
\n
Hello
\n
\n
If we try to pass a list to this function we get an expected exception since the function only knows how to process a string
\n
print_me([\"hello\", \"world\"]) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-36-94b3e61eb86f> in <module> … AttributeError: 'list' object has no attribute 'capitalize'
\n
We could create a wrapper function that checked the input and iterated over the individual items if arg is a list. The works but we don't want to have to do this for every function that we want to have flexible input!
\n
def print_me_list(arg): if isinstance(arg, list): for item in arg: print_me(item) else: print_me(arg)
In the example below, the outer function dont_care_func defines an inner function - list_or_str - and then returns this function. The inner function list_or_str is what implements the same \"is-this-a-string-or-list\" logic that we saw in the previous example. Crucially though, it isn't hard-coded to call print_me but calls whatever function is passed (the func parameter) to it from the outer function dont_care_func.
\n
\n
# Our magic wrapper def dont_care_func(func):
def list_or_str(arg): if isinstance(arg, list): for item in arg: func(item) else: func(arg) return list_or_str
\n
\n
How do we use this?
\n
We simply pass the function that we want to wrap to dont_care_func. Recall, that this function just returns an instance of the inner function. In this case the value func will have been replaced by the actual function print_me.
\n
print_stuff = dont_care_func(print_me)
\n
Now we have a wrapped version of print_me that can handle different types of input. Magic!
Pivot functions in MSTICPy and Azure Sentinel Notebooks
\n
Pivot functions attached to entities allow you to quickly find the query, threat intel or enrichment function you need. They support multiple input types and can be chained together to produce powerful pipelines.
\n
\n
","introduction":"","coverImage":null,"coverImageProperties":{"__typename":"CoverImageProperties","style":"STANDARD","titlePosition":"BOTTOM","altText":""},"currentRevision":{"__ref":"Revision:revision:2151112_6"},"latestVersion":{"__typename":"FriendlyVersion","major":"2","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":3960},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[]},"blogMessagePolicies":{"__typename":"BlogMessagePolicies","canDoAuthoringActionsOnBlog":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","key":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":6}},"Conversation:conversation:2151112":{"__typename":"Conversation","id":"conversation:2151112","solved":false,"topic":{"__ref":"BlogTopicMessage:message:2151112"},"lastPostingActivityTime":"2021-03-23T16:46:40.277-07:00","lastPostTime":"2021-02-23T10:19:33.539-08:00","unreadReplyCount":0,"isSubscribed":false},"ModerationData:moderation_data:2151112":{"__typename":"ModerationData","id":"moderation_data:2151112","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2NWk0RkQ2QzgyNEExMUQzRjVG?revision=6\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2NWk0RkQ2QzgyNEExMUQzRjVG?revision=6","title":"PivotBrowser.png","associationType":"TEASER","width":1548,"height":1291,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2NGlGMTg4Q0RGRUZERDBEQTY2?revision=6\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2NGlGMTg4Q0RGRUZERDBEQTY2?revision=6","title":"Tab-Completion.png","associationType":"BODY","width":697,"height":289,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2M2lFOTEwQzNEQTE5RjlDNTFG?revision=6\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTUxMTEyLTI1NjI2M2lFOTEwQzNEQTE5RjlDNTFG?revision=6","title":"PivotBrowser.png","associationType":"BODY","width":1548,"height":1291,"altText":null},"Revision:revision:2151112_6":{"__typename":"Revision","id":"revision:2151112_6","lastEditTime":"2021-03-23T16:46:40.277-07:00"},"CachedAsset:theme:customTheme1-1744326567472":{"__typename":"CachedAsset","id":"theme:customTheme1-1744326567472","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["default"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"favicon-1730836283320.png","imageLastModified":"1730836286415","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"favicon-1730836271365.png","imageLastModified":"1730836274203","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1300px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_BROWSER","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"3px","borderRadius":"3px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"16px","paddingXHero":"60px","fontStyle":"NORMAL","fontWeight":"700","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-200)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-200)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"LIGHT","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.16)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.12)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-link-color)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","forumColor":"#4099E2","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#148563","blogColor":"#1CBAA0","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#4C6B90","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#FF8000","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#D13A1F","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#333333","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#717171","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0069D4","secondary":"#333333","bodyText":"#1E1E1E","bodyBg":"#FFFFFF","info":"#409AE2","success":"#41C5AE","warning":"#FCC844","danger":"#BC341B","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#D3F5A4","#243A5E"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Segoe UI","fontStyle":"NORMAL","fontWeight":"400","h1FontSize":"34px","h2FontSize":"32px","h3FontSize":"28px","h4FontSize":"24px","h5FontSize":"20px","h6FontSize":"16px","lineHeight":"1.3","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":"","imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"40px","defaultMessageHeaderMarginBottom":"20px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"40px","specialMessageHeaderMarginBottom":"20px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Segoe UI","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.5","fontSizeBase":"16px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"14px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[{"source":"SERVER","name":"Segoe UI","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"},{"style":"NORMAL","weight":"300","__typename":"FontStyleData"},{"style":"NORMAL","weight":"600","__typename":"FontStyleData"},{"style":"NORMAL","weight":"700","__typename":"FontStyleData"},{"style":"ITALIC","weight":"400","__typename":"FontStyleData"}],"assetNames":["SegoeUI-normal-400.woff2","SegoeUI-normal-300.woff2","SegoeUI-normal-600.woff2","SegoeUI-normal-700.woff2","SegoeUI-italic-400.woff2"],"__typename":"CustomFont"},{"source":"SERVER","name":"MWF Fluent Icons","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"}],"assetNames":["MWFFluentIcons-normal-400.woff2"],"__typename":"CustomFont"}],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1744658874071","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1744658874071","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:o365.prod:pages/blogs/BlogMessagePage:board:MicrosoftSentinelBlog-1745160785820":{"__typename":"CachedAsset","id":"quilt:o365.prod:pages/blogs/BlogMessagePage:board:MicrosoftSentinelBlog-1745160785820","value":{"id":"BlogMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"blog-article","layout":"ONE_COLUMN","bgColor":null,"showTitle":null,"showDescription":null,"textPosition":null,"textColor":null,"sectionEditLevel":"LOCKED","bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"OneColumnQuiltSection","columnMap":{"main":[{"id":"blogs.widget.blogArticleWidget","className":"lia-blog-container","props":null,"__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"}},{"id":"section-1729184836777","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":false,"showDescription":false,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[],"side":[],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-pages/blogs/BlogMessagePage-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-pages/blogs/BlogMessagePage-1744658874071","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This blog post cannot be found","name":"Blog Message Page","section.blog-article.title":"Blog Post","archivedMessageTitle":"This Content Has Been Archived","section.section-1729184836777.title":"","section.section-1729184836777.description":"","section.CncIde.title":"Blog Post","section.tifEmD.description":"","section.tifEmD.title":""},"localOverride":false},"CachedAsset:quiltWrapper:o365.prod:Common:1744410784317":{"__typename":"CachedAsset","id":"quiltWrapper:o365.prod:Common:1744410784317","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"community.widget.navbarWidget","props":{"showUserName":true,"showRegisterLink":true,"useIconLanguagePicker":true,"useLabelLanguagePicker":true,"className":"QuiltComponent_lia-component-edit-mode__0nCcm","links":{"sideLinks":[],"mainLinks":[{"children":[],"linkType":"INTERNAL","id":"gxcuf89792","params":{},"routeName":"CommunityPage"},{"children":[],"linkType":"EXTERNAL","id":"external-link","url":"/Directory","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft365","params":{"categoryId":"microsoft365"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-teams","params":{"categoryId":"MicrosoftTeams"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows","params":{"categoryId":"Windows"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-securityand-compliance","params":{"categoryId":"microsoft-security"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"outlook","params":{"categoryId":"Outlook"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"planner","params":{"categoryId":"Planner"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows-server","params":{"categoryId":"Windows-Server"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"azure","params":{"categoryId":"Azure"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"exchange","params":{"categoryId":"Exchange"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-endpoint-manager","params":{"categoryId":"microsoft-endpoint-manager"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-q-l-server","params":{"categoryId":"SQL-Server"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-2","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities","url":"/","target":"BLANK"},{"children":[{"linkType":"INTERNAL","id":"education-sector","params":{"categoryId":"EducationSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"a-i","params":{"categoryId":"AI"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"i-t-ops-talk","params":{"categoryId":"ITOpsTalk"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"partner-community","params":{"categoryId":"PartnerCommunity"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-mechanics","params":{"categoryId":"MicrosoftMechanics"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"healthcare-and-life-sciences","params":{"categoryId":"HealthcareAndLifeSciences"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"public-sector","params":{"categoryId":"PublicSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"io-t","params":{"categoryId":"IoT"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"driving-adoption","params":{"categoryId":"DrivingAdoption"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-m-b","params":{"categoryId":"SMB"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"startupsat-microsoft","params":{"categoryId":"StartupsatMicrosoft"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-1","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities-1","url":"/","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external","url":"/Blogs","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external-1","url":"/Events","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft-learn-1","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-learn-blog","params":{"boardId":"MicrosoftLearnBlog","categoryId":"MicrosoftLearn"},"routeName":"BlogBoardPage"},{"linkType":"EXTERNAL","id":"external-10","url":"https://learningroomdirectory.microsoft.com/","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-3","url":"https://docs.microsoft.com/learn/dynamics365/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-4","url":"https://docs.microsoft.com/learn/m365/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-5","url":"https://docs.microsoft.com/learn/topics/sci/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-6","url":"https://docs.microsoft.com/learn/powerplatform/?wt.mc_id=techcom_header-webpage-powerplatform","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-7","url":"https://docs.microsoft.com/learn/github/?wt.mc_id=techcom_header-webpage-github","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-8","url":"https://docs.microsoft.com/learn/teams/?wt.mc_id=techcom_header-webpage-teams","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-9","url":"https://docs.microsoft.com/learn/dotnet/?wt.mc_id=techcom_header-webpage-dotnet","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-2","url":"https://docs.microsoft.com/learn/azure/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"}],"linkType":"INTERNAL","id":"microsoft-learn","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"community-info-center","params":{"categoryId":"Community-Info-Center"},"routeName":"CategoryPage"}]},"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","controllerHighlightColor":"hsla(30, 100%, 50%)","linkFontWeight":"400","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkBoxShadowHover":"none","linkFontSize":"14px","backgroundOpacity":0.8,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","hamburgerColor":"var(--lia-nav-controller-icon-color)","linkTextBorderBottom":"none","brandLogoHeight":"30px","linkBgHoverColor":"transparent","linkLetterSpacing":"normal","collapseMenuDividerOpacity":0.16,"dropdownPaddingBottom":"15px","paddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"1px solid var(--lia-bs-border-color)","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","collapseMenuDividerBg":"var(--lia-nav-link-color)","linkColor":"var(--lia-bs-body-color)","linkJustifyContent":"flex-start","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","controllerTextColor":"var(--lia-nav-controller-icon-color)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-body-color)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid var(--lia-bs-body-color)","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","linkPaddingX":"10px","linkPaddingY":"5px","paddingTop":"15px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkBgColor":"transparent","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkDropdownPaddingY":"9px","controllerIconColor":"var(--lia-bs-body-color)","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"var(--lia-bs-body-color)"},"showSearchIcon":false,"languagePickerStyle":"iconAndLabel"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"transparent","linkHighlightColor":"var(--lia-bs-primary)","visualEffects":{"showBottomBorder":true},"linkTextColor":"var(--lia-bs-gray-700)"},"__typename":"QuiltComponent"},{"id":"custom.widget.community_banner","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"usePageWidth":false,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.HeroBanner","props":{"widgetVisibility":"signedInOrAnonymous","usePageWidth":false,"useTitle":true,"cMax_items":3,"useBackground":false,"title":"","lazyLoad":false,"widgetChooser":"custom.widget.HeroBanner"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.MicrosoftFooter","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1744658874071","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.community_banner-en-1744400828360":{"__typename":"CachedAsset","id":"component:custom.widget.community_banner-en-1744400828360","value":{"component":{"id":"custom.widget.community_banner","template":{"id":"community_banner","markupLanguage":"HANDLEBARS","style":".community-banner {\n a.top-bar.btn {\n top: 0px;\n width: 100%;\n z-index: 999;\n text-align: center;\n left: 0px;\n background: #0068b8;\n color: white;\n padding: 10px 0px;\n display: block;\n box-shadow: none !important;\n border: none !important;\n border-radius: none !important;\n margin: 0px !important;\n font-size: 14px;\n }\n}\n","texts":null,"defaults":{"config":{"applicablePages":[],"description":"community announcement text","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.community_banner","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"community announcement text","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_community_banner_community-banner_1x9u2_1 {\n a.custom_widget_community_banner_top-bar_1x9u2_2.custom_widget_community_banner_btn_1x9u2_2 {\n top: 0;\n width: 100%;\n z-index: 999;\n text-align: center;\n left: 0;\n background: #0068b8;\n color: white;\n padding: 0.625rem 0;\n display: block;\n box-shadow: none !important;\n border: none !important;\n border-radius: none !important;\n margin: 0 !important;\n font-size: 0.875rem;\n }\n}\n","tokens":{"community-banner":"custom_widget_community_banner_community-banner_1x9u2_1","top-bar":"custom_widget_community_banner_top-bar_1x9u2_2","btn":"custom_widget_community_banner_btn_1x9u2_2"}},"form":null},"localOverride":false},"CachedAsset:component:custom.widget.HeroBanner-en-1744400828360":{"__typename":"CachedAsset","id":"component:custom.widget.HeroBanner-en-1744400828360","value":{"component":{"id":"custom.widget.HeroBanner","template":{"id":"HeroBanner","markupLanguage":"REACT","style":null,"texts":{"searchPlaceholderText":"Search this community","followActionText":"Follow","unfollowActionText":"Following","searchOnHoverText":"Please enter your search term(s) and then press return key to complete a search.","blogs.sidebar.pagetitle":"Latest Blogs | Microsoft Tech Community","followThisNode":"Follow this node","unfollowThisNode":"Unfollow this node"},"defaults":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.HeroBanner","form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"__typename":"Component","localOverride":false},"globalCss":null,"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"}},"localOverride":false},"CachedAsset:component:custom.widget.MicrosoftFooter-en-1744400828360":{"__typename":"CachedAsset","id":"component:custom.widget.MicrosoftFooter-en-1744400828360","value":{"component":{"id":"custom.widget.MicrosoftFooter","template":{"id":"MicrosoftFooter","markupLanguage":"HANDLEBARS","style":".context-uhf {\n min-width: 280px;\n font-size: 15px;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.c-uhff-link {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.c-uhff {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.c-uhff-nav {\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n .c-heading-4 {\n color: #616161;\n word-break: break-word;\n font-size: 15px;\n line-height: 20px;\n padding: 36px 0 4px;\n font-weight: 600;\n }\n .c-uhff-nav-row {\n .c-uhff-nav-group {\n display: block;\n float: left;\n min-height: 1px;\n vertical-align: text-top;\n padding: 0 12px;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.c-list.f-bare {\n font-size: 11px;\n line-height: 16px;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 8px 0;\n margin: 0;\n }\n }\n }\n }\n}\n.c-uhff-base {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 30px 5% 16px;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.c-uhff-ccpa {\n font-size: 11px;\n line-height: 16px;\n float: left;\n margin: 3px 0;\n }\n a.c-uhff-ccpa:hover {\n text-decoration: underline;\n }\n ul.c-list {\n font-size: 11px;\n line-height: 16px;\n float: right;\n margin: 3px 0;\n color: #616161;\n li {\n padding: 0 24px 4px 0;\n display: inline-block;\n }\n }\n .c-list.f-bare {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 30px 24px 16px;\n }\n}\n\n.social-share {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n\n.sharing-options {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 43px;\n border-radius: 0px 7px 7px 0px;\n}\n.linkedin-icon {\n border-top-right-radius: 7px;\n}\n.linkedin-icon:hover {\n border-radius: 0;\n}\n.social-share-rss-image {\n border-bottom-right-radius: 7px;\n}\n.social-share-rss-image:hover {\n border-radius: 0;\n}\n\n.social-link-footer {\n position: relative;\n display: block;\n margin: -2px 0;\n transition: all 0.2s ease;\n}\n.social-link-footer:hover .linkedin-icon {\n border-radius: 0;\n}\n.social-link-footer:hover .social-share-rss-image {\n border-radius: 0;\n}\n\n.social-link-footer img {\n width: 40px;\n height: auto;\n transition: filter 0.3s ease;\n}\n\n.social-share-list {\n width: 40px;\n}\n.social-share-rss-image {\n width: 40px;\n}\n\n.share-icon {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n\n.share-icon:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n\n.share-icon:hover .label {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n\n.label {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 10px;\n top: 50%;\n transform: translateY(-50%);\n height: 40px;\n border-radius: 0 6px 6px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px 5px 20px 8px;\n margin-left: -1px;\n}\n.linkedin {\n background-color: #0474b4;\n}\n.facebook {\n background-color: #3c5c9c;\n}\n.twitter {\n background-color: white;\n color: black;\n}\n.reddit {\n background-color: #fc4404;\n}\n.mail {\n background-color: #848484;\n}\n.bluesky {\n background-color: white;\n color: black;\n}\n.rss {\n background-color: #ec7b1c;\n}\n#RSS {\n width: 40px;\n height: 40px;\n}\n\n@media (max-width: 991px) {\n .social-share {\n display: none;\n }\n}\n","texts":{"New tab":"What's New","New 1":"Surface Laptop Studio 2","New 2":"Surface Laptop Go 3","New 3":"Surface Pro 9","New 4":"Surface Laptop 5","New 5":"Surface Studio 2+","New 6":"Copilot in Windows","New 7":"Microsoft 365","New 8":"Windows 11 apps","Store tab":"Microsoft Store","Store 1":"Account Profile","Store 2":"Download Center","Store 3":"Microsoft Store Support","Store 4":"Returns","Store 5":"Order tracking","Store 6":"Certified Refurbished","Store 7":"Microsoft Store Promise","Store 8":"Flexible Payments","Education tab":"Education","Edu 1":"Microsoft in education","Edu 2":"Devices for education","Edu 3":"Microsoft Teams for Education","Edu 4":"Microsoft 365 Education","Edu 5":"How to buy for your school","Edu 6":"Educator Training and development","Edu 7":"Deals for students and parents","Edu 8":"Azure for students","Business tab":"Business","Bus 1":"Microsoft Cloud","Bus 2":"Microsoft Security","Bus 3":"Dynamics 365","Bus 4":"Microsoft 365","Bus 5":"Microsoft Power Platform","Bus 6":"Microsoft Teams","Bus 7":"Microsoft Industry","Bus 8":"Small Business","Developer tab":"Developer & IT","Dev 1":"Azure","Dev 2":"Developer Center","Dev 3":"Documentation","Dev 4":"Microsoft Learn","Dev 5":"Microsoft Tech Community","Dev 6":"Azure Marketplace","Dev 7":"AppSource","Dev 8":"Visual Studio","Company tab":"Company","Com 1":"Careers","Com 2":"About Microsoft","Com 3":"Company News","Com 4":"Privacy at Microsoft","Com 5":"Investors","Com 6":"Diversity and inclusion","Com 7":"Accessiblity","Com 8":"Sustainibility"},"defaults":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.MicrosoftFooter","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_MicrosoftFooter_context-uhf_105bp_1 {\n min-width: 17.5rem;\n font-size: 0.9375rem;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-link_105bp_12 {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff_105bp_12 {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35 {\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n .custom_widget_MicrosoftFooter_c-heading-4_105bp_49 {\n color: #616161;\n word-break: break-word;\n font-size: 0.9375rem;\n line-height: 1.25rem;\n padding: 2.25rem 0 0.25rem;\n font-weight: 600;\n }\n .custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57 {\n .custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58 {\n display: block;\n float: left;\n min-height: 0.0625rem;\n vertical-align: text-top;\n padding: 0 0.75rem;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 0.5rem 0;\n margin: 0;\n }\n }\n }\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff-base_105bp_94 {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 1.875rem 5% 1rem;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: left;\n margin: 0.1875rem 0;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107:hover {\n text-decoration: underline;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: right;\n margin: 0.1875rem 0;\n color: #616161;\n li {\n padding: 0 1.5rem 0.25rem 0;\n display: inline-block;\n }\n }\n .custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 1.875rem 1.5rem 1rem;\n }\n}\n.custom_widget_MicrosoftFooter_social-share_105bp_138 {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n.custom_widget_MicrosoftFooter_sharing-options_105bp_146 {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 2.6875rem;\n border-radius: 0 0.4375rem 0.4375rem 0;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-top-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-bottom-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 {\n position: relative;\n display: block;\n margin: -0.125rem 0;\n transition: all 0.2s ease;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 img {\n width: 2.5rem;\n height: auto;\n transition: filter 0.3s ease;\n}\n.custom_widget_MicrosoftFooter_social-share-list_105bp_188 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195 {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover .custom_widget_MicrosoftFooter_label_105bp_207 {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n.custom_widget_MicrosoftFooter_label_105bp_207 {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 0.625rem;\n top: 50%;\n transform: translateY(-50%);\n height: 2.5rem;\n border-radius: 0 0.375rem 0.375rem 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.25rem 0.3125rem 1.25rem 0.5rem;\n margin-left: -0.0625rem;\n}\n.custom_widget_MicrosoftFooter_linkedin_105bp_156 {\n background-color: #0474b4;\n}\n.custom_widget_MicrosoftFooter_facebook_105bp_237 {\n background-color: #3c5c9c;\n}\n.custom_widget_MicrosoftFooter_twitter_105bp_240 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_reddit_105bp_244 {\n background-color: #fc4404;\n}\n.custom_widget_MicrosoftFooter_mail_105bp_247 {\n background-color: #848484;\n}\n.custom_widget_MicrosoftFooter_bluesky_105bp_250 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_rss_105bp_254 {\n background-color: #ec7b1c;\n}\n#custom_widget_MicrosoftFooter_RSS_105bp_1 {\n width: 2.5rem;\n height: 2.5rem;\n}\n@media (max-width: 991px) {\n .custom_widget_MicrosoftFooter_social-share_105bp_138 {\n display: none;\n }\n}\n","tokens":{"context-uhf":"custom_widget_MicrosoftFooter_context-uhf_105bp_1","c-uhff-link":"custom_widget_MicrosoftFooter_c-uhff-link_105bp_12","c-uhff":"custom_widget_MicrosoftFooter_c-uhff_105bp_12","c-uhff-nav":"custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35","c-heading-4":"custom_widget_MicrosoftFooter_c-heading-4_105bp_49","c-uhff-nav-row":"custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57","c-uhff-nav-group":"custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58","c-list":"custom_widget_MicrosoftFooter_c-list_105bp_78","f-bare":"custom_widget_MicrosoftFooter_f-bare_105bp_78","c-uhff-base":"custom_widget_MicrosoftFooter_c-uhff-base_105bp_94","c-uhff-ccpa":"custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107","social-share":"custom_widget_MicrosoftFooter_social-share_105bp_138","sharing-options":"custom_widget_MicrosoftFooter_sharing-options_105bp_146","linkedin-icon":"custom_widget_MicrosoftFooter_linkedin-icon_105bp_156","social-share-rss-image":"custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162","social-link-footer":"custom_widget_MicrosoftFooter_social-link-footer_105bp_169","social-share-list":"custom_widget_MicrosoftFooter_social-share-list_105bp_188","share-icon":"custom_widget_MicrosoftFooter_share-icon_105bp_195","label":"custom_widget_MicrosoftFooter_label_105bp_207","linkedin":"custom_widget_MicrosoftFooter_linkedin_105bp_156","facebook":"custom_widget_MicrosoftFooter_facebook_105bp_237","twitter":"custom_widget_MicrosoftFooter_twitter_105bp_240","reddit":"custom_widget_MicrosoftFooter_reddit_105bp_244","mail":"custom_widget_MicrosoftFooter_mail_105bp_247","bluesky":"custom_widget_MicrosoftFooter_bluesky_105bp_250","rss":"custom_widget_MicrosoftFooter_rss_105bp_254","RSS":"custom_widget_MicrosoftFooter_RSS_105bp_1"}},"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1744658874071","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1744658874071","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"Category:category:Exchange":{"__typename":"Category","id":"category:Exchange","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Planner":{"__typename":"Category","id":"category:Planner","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Outlook":{"__typename":"Category","id":"category:Outlook","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Community-Info-Center":{"__typename":"Category","id":"category:Community-Info-Center","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:EducationSector":{"__typename":"Category","id":"category:EducationSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:DrivingAdoption":{"__typename":"Category","id":"category:DrivingAdoption","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Azure":{"__typename":"Category","id":"category:Azure","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows-Server":{"__typename":"Category","id":"category:Windows-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:SQL-Server":{"__typename":"Category","id":"category:SQL-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftTeams":{"__typename":"Category","id":"category:MicrosoftTeams","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PublicSector":{"__typename":"Category","id":"category:PublicSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft365":{"__typename":"Category","id":"category:microsoft365","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:IoT":{"__typename":"Category","id":"category:IoT","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:HealthcareAndLifeSciences":{"__typename":"Category","id":"category:HealthcareAndLifeSciences","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:SMB":{"__typename":"Category","id":"category:SMB","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:ITOpsTalk":{"__typename":"Category","id":"category:ITOpsTalk","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft-endpoint-manager":{"__typename":"Category","id":"category:microsoft-endpoint-manager","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftLearn":{"__typename":"Category","id":"category:MicrosoftLearn","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Blog:board:MicrosoftLearnBlog":{"__typename":"Blog","id":"board:MicrosoftLearnBlog","blogPolicies":{"__typename":"BlogPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:AI":{"__typename":"Category","id":"category:AI","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftMechanics":{"__typename":"Category","id":"category:MicrosoftMechanics","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:StartupsatMicrosoft":{"__typename":"Category","id":"category:StartupsatMicrosoft","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PartnerCommunity":{"__typename":"Category","id":"category:PartnerCommunity","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows":{"__typename":"Category","id":"category:Windows","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"QueryVariables:TopicReplyList:message:2151112:6":{"__typename":"QueryVariables","id":"TopicReplyList:message:2151112:6","value":{"id":"message:2151112","first":10,"sorts":{"postTime":{"direction":"DESC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"DESC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-components/community/Navbar-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1744658874071","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","gxcuf89792":"Tech Community","external-1":"Events","s-m-b":"Small and Medium Businesses","windows-server":"Windows Server","education-sector":"Education Sector","driving-adoption":"Driving Adoption","microsoft-learn":"Microsoft Learn","s-q-l-server":"SQL Server","partner-community":"Microsoft Partner Community","microsoft365":"Microsoft 365","external-9":".NET","external-8":"Teams","external-7":"Github","products-services":"Products","external-6":"Power Platform","communities-1":"Topics","external-5":"Microsoft Security","planner":"Planner","external-4":"Microsoft 365","external-3":"Dynamics 365","azure":"Azure","healthcare-and-life-sciences":"Healthcare and Life Sciences","external-2":"Azure","microsoft-mechanics":"Microsoft Mechanics","microsoft-learn-1":"Community","external-10":"Learning Room Directory","microsoft-learn-blog":"Blog","windows":"Windows","i-t-ops-talk":"ITOps Talk","external-link-1":"View All","microsoft-securityand-compliance":"Microsoft Security","public-sector":"Public Sector","community-info-center":"Lounge","external-link-2":"View All","microsoft-teams":"Microsoft Teams","external":"Blogs","microsoft-endpoint-manager":"Microsoft Intune and Configuration Manager","startupsat-microsoft":"Startups at Microsoft","exchange":"Exchange","a-i":"AI and Machine Learning","io-t":"Internet of Things (IoT)","outlook":"Outlook","external-link":"Community Hubs","communities":"Products"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1744658874071","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1744658874071","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1744658874071","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1744658874071","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1744658874071","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1744658874071","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solved","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1744658874071","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1744658874071","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1744658874071","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1744658874071","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCoverImage-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCoverImage-1744658874071","value":{"coverImageTitle":"Cover Image"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeTitle-1744658874071","value":{"nodeTitle":"{nodeTitle, select, community {Community} other {{nodeTitle}}} "},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTimeToRead-1744658874071","value":{"minReadText":"{min} MIN READ"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1744658874071","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1744658874071","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1744658874071","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1744658874071","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1744658874071","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1744658874071","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1744658874071","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1744658874071","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1744658874071","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1744658874071","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1744658874071","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1744658874071","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeAvatar-1744658874071","value":{"altTitle":"Node avatar for {nodeTitle}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeDescription-1744658874071","value":{"description":"{description}"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1744658874071","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1744658874071":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1744658874071","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false}}}},"page":"/blogs/BlogMessagePage/BlogMessagePage","query":{"boardId":"microsoftsentinelblog","messageSubject":"jupyter-notebook-pivot-functions","messageId":"2151112"},"buildId":"HEhyUrv5OXNBIbfCLaOrw","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"o365","openTelemetryServiceVersion":"25.1.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/customComponent/CustomComponent/CustomComponent.tsx","./components/blogs/BlogArticleWidget/BlogArticleWidget.tsx","./components/external/components/ExternalComponent.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx"],"appGip":true,"scriptLoader":[{"id":"analytics","src":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/pagescripts/1730819800000/analytics.js?page.id=BlogMessagePage&entity.id=board%3Amicrosoftsentinelblog&entity.id=message%3A2151112","strategy":"afterInteractive"}]}